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

时间:2014-04-03 20:01:23

标签: c# arrays linq list functional-programming

假设我有一个integeres数组,我想把它分成几个部分,我想用零作为断开时间的条件。这样的事情:

[1,2,3,0,4,5,0,6,7] => [[1,2,3,0], [4,5,0], [6,7]]

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

有一些问题,例如 [1][2] ,但与此相反,它们依赖于从列表外部提供的条件。

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

3 个答案:

答案 0 :(得分:10)

您的集合中的各个元素之间存在依赖关系,具体而言,对于您想要知道的每个元素,前一个元素为零?"。一旦您的查询依赖于前一个元素(或者更常见的是,只要您的查询依赖于同一序列的其他元素),您就应该达到Aggregate(或更一般的函数式编程术语,{ {1}})。这是因为fold与其他LINQ运算符不同,它允许您随身携带状态从一次迭代到下一次迭代。

因此,为了回答您的问题,我在LINQ中编写了如下查询。

Aggregate

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

// 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;
                                   });

正如我之前所说,因为我们需要携带状态,所以要达到Aggregate。将新的空列表放入列表列表中会删除values.Aggregate(new List<List<int>>{new List<int>()}, 的边缘大小写,其中没有列表。

List<List<int>>

再次,查看我们的lambda表达式((list, value) => {...} )的签名,我们可以看到明确传递的状态:我们接受Func<List<List<int>>, int, List<List<int>>并返回相同的内容。

List<List<int>>

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

Last()

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

if (value == 0) list.Add(new List<int>());

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


return list; 方法中,这可以很容易地推广,如下所示:

SplitOn

由于Enumerables的工作方式,使用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的版本不太清楚,但同样,并不是特别难以从上面的代码创建,看起来像(通过三元运算符简化了触摸):

List

你也可能会发现Haskell's implementation of splitOn很有趣,因为它正是你想要的。我会称之为不平凡(轻描淡写)。

答案 1 :(得分:1)

这是一个有助于扩展的扩展程序:

public static IEnumerable<Tuple<TIn, int>> MarkWithLabels<TIn>(this IEnumerable<TIn> src, Predicate<TIn> splittingCondition)
{
    int label = 0;
    foreach (TIn item in src)
    {
        yield return new Tuple<TIn, int>(item, label);
        if (splittingCondition(item))
            label++;
    }
}

有了它,下面的技巧

int breakingValue = 0;
var subseq = seq.MarkWithLabels(i => i == breakingValue)
    .GroupBy(tup => tup.Item2)
    .Select(group => group.Select(tup => tup.Item1).ToArray())
    .ToArray();
除了foreach之外,FP解决方案可以基本相同。

答案 2 :(得分:0)

我完全基于Zack's回答编译了两个扩展方法。

     [root@radiusx1 ~]# systemctl start radiusd
    Warning: radiusd.service changed on disk. Run 'systemctl daemon-reload' to reload units.
    Job for radiusd.service failed because the control process exited with error code. See "systemctl status radiusd.service" and "journalctl -xe" for details.


    [root@radiusx1 ~]# systemctl status radiusd
● radiusd.service - FreeRADIUS high performance RADIUS server.
   Loaded: loaded (/usr/lib/systemd/system/radiusd.service; enabled; vendor preset: disabled)
   Active: failed (Result: exit-code) since Thu 2017-03-02 17:21:00 PKT; 10s ago
  Process: 26712 ExecStartPre=/usr/sbin/radiusd -C (code=exited, status=1/FAILURE)
  Process: 26708 ExecStartPre=/bin/chown -R radiusd.radiusd /var/run/radiusd (code=exited, status=0/SUCCESS)

Mar 02 17:20:59 radiusx1.xoultech.com systemd[1]: Starting FreeRADIUS high performance RADIUS server....
Mar 02 17:21:00 radiusx1.xoultech.com systemd[1]: radiusd.service: control process exited, code=exited status=1
Mar 02 17:21:00 radiusx1.xoultech.com systemd[1]: Failed to start FreeRADIUS high performance RADIUS server..
Mar 02 17:21:00 radiusx1.xoultech.com systemd[1]: Unit radiusd.service entered failed state.
Mar 02 17:21:00 radiusx1.xoultech.com systemd[1]: radiusd.service failed.