我最近遇到的情况是我需要执行一个慢慢产生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中,组首次遇到时会立即返回,因此可以在不等待整个序列分组的情况下执行操作。
思想?
编辑:快速思考,我认为“懒惰”不是正确的术语......我不是母语人士,我实际上在寻找什么术语?
答案 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);
}
}
}
缺点是导致过滤将应用于每个组的整个源,但通常当懒惰评估是必须的时候由于延迟而不是端到端速度