您在集合的不同元素之间存在依赖关系,特别是对于您想知道“前一个元素是否为零?”的每个元素。只要您的查询依赖于前一个元素(或者,更一般地说,只要您的查询依赖于相同序列的其他元素),您就应该使用Aggregate(或更一般的函数式编程术语,fold )。这是因为Aggregate 与其他 LINQ 运算符不同,它允许您将状态从一个迭代带到下一个迭代。
所以,为了回答您的问题,我将在 LINQ 中编写如下查询。
// assume our list of integers it called values
var splitByZero = values.Aggregate(new List<List<int>>{new List<int>()},
(list, value) => {
list.Last().Add(value);
if (value == 0) list.Add(new List<int>());
return list;
});
我将把它分解成几个部分,以便更好地解释我的想法。
values.Aggregate(new List<List<int>>{new List<int>()},
正如我之前所说,因为我们需要携带状态,所以使用 Aggregate。将一个新的空列表放入我们的列表列表中可以消除 List<List<int>> 中没有列表的边缘情况。
(list, value) => {...}
再次查看 lambda 表达式的签名(Func<List<List<int>>, int, List<List<int>>),我们可以看到状态显式传递:我们接受 List<List<int>> 并返回相同的值。
list.Last().Add(value);
由于我们总是希望处理最新的List<int>,因此我们得到了列表列表中的Last() 元素(由于上面的部分,它永远不会为空)。
if (value == 0) list.Add(new List<int>());
这是我们进行拆分的地方 - 在下一次迭代中,对 Last() 的调用将返回这个新列表。
return list;
我们最终将状态传递给下一次迭代。
这可以用SplitOn 方法轻松概括,如下所示:
public static IEnumerable<IEnumerable<T>> SplitOn<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
return source.Aggregate(new List<List<T>> {new List<T>()},
(list, value) =>
{
list.Last().Add(value);
if (predicate(value)) list.Add(new List<T>());
return list;
});
}
使用IEnumerable's 而不是List's 的版本不太清楚,因为 Enumerables 的工作方式,但同样,从上面的代码创建并不是特别难,看起来像(简化只是通过三元运算符进行触摸):
public static IEnumerable<IEnumerable<T>> SplitOn<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
return source.Aggregate(Enumerable.Repeat(Enumerable.Empty<T>(), 1),
(list, value) =>
{
list.Last().Concat(Enumerable.Repeat(value, 1));
return predicate(value) ? list.Concat(Enumerable.Repeat(Enumerable.Empty<T>(), 1)) : list;
});
}
您还可能会发现Haskell's implementation of splitOn 很有趣,因为它完全符合您的要求。我会称之为不平凡(说得轻巧)。