“Lazy”Group与Linq合作

时间:2010-08-08 08:11:57

标签: c# linq

我最近遇到的情况是我需要执行一个慢慢产生Linq查询的操作。

现在,groupBy失去了它的懒惰,这意味着你必须等待整个序列完成,直到你得到任何组返回。 这对我来说在逻辑上似乎不是最好的解决方案,因为一个组可以在第一次遇到时立即返回。

我编写了以下代码,它似乎工作正常,我正在寻找陷阱和一般改进,以及对概念本身的想法(例如,can / should groupBy方法可以尽快返回组)。

public static IEnumerable<KeyValuePair<R, IEnumerable<T>>> GroupByLazy<T, R>(this IEnumerable<T> source, Func<T, R> keySelector)
        {
            var dic = new Dictionary<R, BlockingCollection<T>>();
            foreach (var item in source)
            {
                var Key = keySelector(item);
                BlockingCollection<T> i;
                if (!dic.TryGetValue(Key, out i))
                {
                    i = new BlockingCollection<T>();
                    i.Add(item);
                    dic.Add(Key, i);
                    yield return new KeyValuePair<R, IEnumerable<T>>(Key, i);
                }
                else i.TryAdd(item);
            }
            // mark all the groups as completed so that enumerations of group-items can finish
            foreach (var groupedValues in dic.Values)
                groupedValues.CompleteAdding();
        }

简单测试:

var slowIE = Observable.Interval(TimeSpan.FromSeconds(1)).ToEnumerable().Take(10);
            var debug = slowIE.Do(i => Console.WriteLine("\teval " + i));

            var gl = debug.GroupByLazy(i => i % 2 == 0);

            var g = debug.GroupBy(i => i % 2 == 0);

            Console.WriteLine("Lazy:");
            gl.Run(i => Console.WriteLine("Group returned: " + i.Key));
            Console.WriteLine(gl.Single(i => i.Key).Value.Count());

            Console.WriteLine("NonLazy:");
            g.Run(i => Console.WriteLine("Group returned: " + i.Key));
            Console.WriteLine(g.Single(i => i.Key).Count());

            Console.ReadLine();

打印:

Lazy:
        eval 0
Group returned: True
        eval 1
Group returned: False
        eval 2
        eval 3
        eval 4
        eval 5
        eval 6
        eval 7
        eval 8
        eval 9
NonLazy:
        eval 0
        eval 1
        eval 2
        eval 3
        eval 4
        eval 5
        eval 6
        eval 7
        eval 8
        eval 9
Group returned: True
Group returned: False

正如您所看到的,在我的LazyGroup中,组首次遇到时会立即返回,因此可以在不等待整个序列分组的情况下执行操作。

思想?

编辑:快速思考,我认为“懒惰”不是正确的术语......我不是母语人士,我实际上在寻找什么术语?

4 个答案:

答案 0 :(得分:4)

在您的解决方案中,返回组后,返回的组似乎会发生变化。这可能适合某些编程模式,但我认为它并不普遍有用。

想象一下,您在第一次返回时处理一个组,然后稍后将一个新项添加到该组中。您如何知道重新处理该组的成员?我想调用者可能永远不会处理某些分组项目。即使调用CompleteAdding,也不会向LazyGroupBy的消费者提供通知。

同样,这可能适合某些情况,但我想不出我何时会随意使用它。

答案 1 :(得分:2)

这很有意思,但你可以为此展示一个真实世界的用例吗?

我认为在大多数现实世界的情况下,你会迭代这些组,并为每个组迭代元素,或者在该组上调用一些聚合函数。在这种情况下,聚合操作无论如何都会阻塞。在这种情况下,使用GroupBy没有任何优势。

另一种情况是你对元素不感兴趣,只对组感兴趣。但是你根本不需要GroupBy - 你可以使用Select then Distinct。

如果您遇到需要这个“懒惰”GroupBy的情况,请将其添加到您的问题中,以提供一些背景和动机。

答案 2 :(得分:1)

这种“懒惰”执行称为延迟执行。

当您返回一个组时,它只包含第一个项目,并且在您获得更多组之前不会添加任何项目。因此,只有在单独的线程中处理组以便主线程可以继续读取集合,或者如果您首先读取所有组然后处理它们时,这种方法才有效,这当然会使延迟处理毫无意义。

此外,如果使用Take限制查询,则方法将无法完成,并且已返回的组可能永远不会完成,因此您必须始终阅读所有组以使组完成。这也意味着您可能仍然在等待永远不存在的数据。

答案 3 :(得分:0)

我会不同寻求它

public static IEnumerable<KeyValuePair<R, IEnumerable<T>>> GroupByLazy<T, R>(this IEnumerable<T> source, Func<T, R> keySelector)
        {
            var set = HashSet();
            foreach (var item in source)
            {
                var Key = keySelector(item);
                if(set.Add(Key))
                {
                   var groupedItems = from i in source 
                                      where keySelector(i) == Key
                                      select i;
                   yield return new KevValuePair<R,IEnumerable<T>>(Key, groupedItems);
                }
            }
        }

缺点是导致过滤将应用于每个组的整个源,但通常当懒惰评估是必须的时候由于延迟而不是端到端速度