通过检查元素的条件将列表拆分为子列表

Rsh*_*Rsh 5 c# linq arrays functional-programming list

假设我有一个integeres数组,我想将它分成几个部分,我想用零作为何时破坏的条件.像这样的东西:

[1,2,3,0,4,5,0,6,7] => [[1,2,3,0], [4,5,0], [6,7]]
Run Code Online (Sandbox Code Playgroud)

好吧,可以使用两个for循环轻松完成,但我想知道是否可以使用LINQ执行此操作.

有这样的问题[1],[2],但与此相反,它们依赖于从列表外部提供的条件.

注意:我知道在一个帖子中提出多个问题是不礼貌的,但是如果有人熟悉函数式编程(因为在本质上,它确实是一个FP问题),我也希望看到他们的观点和这个问题的可能解决方案.

Zac*_*her 12

您在集合的单独元素之间存在依赖关系,特别是对于您想要知道的每个元素"前一个元素是零吗?".一旦您的查询依赖于前一个元素(或者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;
                                   });
Run Code Online (Sandbox Code Playgroud)

我将其分解为部分,以便我能更好地解释我的想法.

values.Aggregate(new List<List<int>>{new List<int>()},
Run Code Online (Sandbox Code Playgroud)

正如我之前所说,因为我们需要携带状态,所以要达到Aggregate.将新的空列表放入列表列表中会删除List<List<int>>其中没有列表的边缘情况.

(list, value) => {...}
Run Code Online (Sandbox Code Playgroud)

再一次,查看我们的lambda表达式的签名(即Func<List<List<int>>, int, List<List<int>>),我们可以看到明确传递的状态:我们接受List<List<int>>并返回相同的状态.

list.Last().Add(value);
Run Code Online (Sandbox Code Playgroud)

由于我们总是想要处理最新的List<int>,我们得到Last()列表列表的元素(由于上面的部分,它将永远不会为null).

if (value == 0) list.Add(new List<int>());
Run Code Online (Sandbox Code Playgroud)

这是我们进行拆分的地方 - 在下一次迭代中,对Last()的调用将返回这个新列表.

return list;
Run Code Online (Sandbox Code Playgroud)

我们最终将状态传递给下一次迭代.


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

由于Enumerables的工作方式,使用IEnumerable's代替List's 的版本有点不太清楚,但同样,从上面的代码创建并不是特别难,看起来像(通过三元运算符简化了触摸):

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

您也可能会发现Haskell对splitOn的实现很有趣,因为它完全符合您的要求.我会称之为不平凡(轻描淡写).