使用 Linq 进行“惰性”GroupBy

chr*_*aut 6 c# linq

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

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

我编写了以下代码,它似乎工作得很好,并且正在寻找陷阱和一般改进,以及对概念本身的想法(例如,可以/应该 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();
        }
Run Code Online (Sandbox Code Playgroud)

简单测试:

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();
Run Code Online (Sandbox Code Playgroud)

打印:

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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,在我的 LazyGroupBy 中,一旦第一次遇到组就会返回,因此可以在不等待整个序列分组的情况下对其进行操作。

想法?

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

Dre*_*kes 5

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

想象一下,您在第一次返回一个组时对其进行处理,然后在稍后的某个时间将一个新项目添加到该组中。你怎么知道要重新处理小组成员?我想调用者可能永远不会处理一些分组的项目。即使CompleteAdding被调用,也不会向 的使用者提供任何通知LazyGroupBy

同样,这可能适合某些情况,但我想不出什么时候会立即使用它。


Guf*_*ffa 4

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

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

另外,您始终必须读取所有组才能使组完整,如果您使用Take限制查询,该方法将无法完成,并且已经返回的组可能永远不会完成。这也意味着您可能有线程仍在等待永远不会存在的数据。