【问题标题】:How do I group by sequence in LINQ?如何在 LINQ 中按顺序分组?
【发布时间】:2012-06-29 11:58:10
【问题描述】:

给定顺序:

["1","A","B","C","2","F","K","L","5","6","P","I","E"]

数字代表我识别为标题的项目,而字母代表我识别为数据的项目。我想将它们关联到这样的组中。

1:A,B,C    
2:F,K,L    
5:    
6:P,I,E

我可以使用枚举器上的 foreach 或 while 循环轻松实现这一点,但是有没有 LINQ 的方式来实现这一点?这是我的领域中反复出现的模式。

【问题讨论】:

    标签: linq c#-4.0 linq-to-objects


    【解决方案1】:

    这是一个使用 LINQ 的解决方案。虽然有点复杂。可能有一些技巧的空间。它看起来并没有那么糟糕,但是使用 foreach 循环可以提高可读性。

    int lastHeaderIndex = default(int);
    Dictionary<string, IEnumerable<string>> groupedItems =
        items.Select((text, index) =>
                     {
                         int number;
                         if (int.TryParse(text, out number))
                         {
                             lastHeaderIndex = index;
                         }
                         return new { HeaderIndex = lastHeaderIndex, Value = text };
                     })
              .GroupBy(item => item.HeaderIndex)
              .ToDictionary(item => item.FirstOrDefault().Value,
                            item => item.Skip(1).Select(arg => arg.Value));
    

    【讨论】:

    • 是的,看起来不太恶心。我将看看是否可以用命名函数(或接受捕获的变量并返回选择器函数的函数)替换第一个选择器函数。我可能会为 GroupBy 使用重载,它允许使用 ResultSelector 而不是 ToDictionary 调用。然后它实际上会读得很好。
    【解决方案2】:

    你可以使用折叠:

    var aggr = new List<Tuple<Int,List<String>>>();
    var res = sequence.Aggregate(aggr, (d, x) => {
        int i;
        if (Int32.TryParse(x, out i)) {
            var newDict = d.Add(new Tuple(i, new List<string>()));
            return newDict;
        } 
        else {
            var newDict = d[d.Count - 1].Item2.Add(x);
            return newDict;
        }
    }).ToDictionary(x => x.Item1, x => x.Item2);
    

    但是,这看起来不太好,因为缺乏对不可变值的支持。另外,我现在无法对此进行测试。

    【讨论】:

    • 有趣。尽管我认识到 FoldR 在 Func prog 中被大量使用,但我并没有那么多地使用聚合。是时候调查一下它可以为我做什么了。
    • 是的,这在函数式编程中非常自然,因为每个递归都可以转换为折叠。如果没有可用的for,那就太好了。顺便说一句,我的代码有效吗?
    • 是的,确实如此。抱歉耽搁了。我用 Tuple.Create 替换了“new Tuple()”并直接使用了“d”,没有任何“newDict”。但是,是的,它奏效了。谢谢。
    • 哇。阅读本文,我想我刚刚升级为程序员。
    【解决方案3】:

    foreach 循环与int.TryParse 应该会有所帮助。来自 LINQ 的“GroupBy”在这里没有多大帮助。

    【讨论】:

    • 是的,这就是我所说的“while”循环。我进行了编辑以包含 foreach。
    【解决方案4】:

    由于这是您领域中的常见模式,请考虑将结果流式传输,而不是将它们全部收集到一个大型内存对象中。

    public static IEnumerable<IList<string>> SplitOnToken(IEnumerable<string> input, Func<string,bool> isSplitToken)
    {
        var set = new List<string>();
        foreach(var item in input)
        {
            if (isSplitToken(item) && set.Any())
            {
                yield return set;
                set = new List<string>();
            }
            set.Add(item);
        }
        if (set.Any())
        {
            yield return set;
        }
    }
    

    示例用法:

    var sequence = new[] { "1", "A", "B", "C", "2", "F", "K", "L", "5", "6", "P", "I", "E" };
    var groups = SplitOnToken(sequence, x => Char.IsDigit(x[0]));
    
    foreach (var @group in groups)
    {
        Console.WriteLine("{0}: {1}", @group[0], String.Join(" ", @group.Skip(1).ToArray()));
    }
    

    输出:

    1: A B C
    2: F K L
    5: 
    6: P I E
    

    【讨论】:

    • 工具为离线批处理作业,数据量不大。但这是需要考虑的事情。特别是因为新组的开始是前一组的明确结束,那么明显的下意识反应应该是屈服。我同意 100%。为了进一步扩展抽象,函数应该接受一个谓词来识别标题。
    【解决方案5】:

    这是我最终使用的。与phg的答案几乎相同的结构。

    基本上,它是一个聚合函数,它维护一个包含以下内容的元组: 1:累计数据。 2:解析器的状态。

    聚合函数执行 if-else 以检查当前检查的项目是组标题还是常规项目。基于此,它会更新数据存储(元组的最后一部分)和/或更改解析器状态(元组的第一部分)。

    在我的例子中,解析器状态是当前活动列表(即将到来的项目将被插入)。

    var sequence = new[]{ "1","A","B","C","2","F","K","L","5","6","P","I","E"};
    var aggr = Tuple.Create(new List<string>(), new Dictionary<int,List<string>>());
    var res = sequence.Aggregate(aggr, (d, x) => {
        int i;
        if (Int32.TryParse(x, out i))
        {
            var newList = new List<string>();
            d.Item2.Add(i,newList);
            return Tuple.Create(newList,d.Item2);
        } else
        {
            d.Item1.Add(x);
            return d;
        }
    },d=>d.Item2);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-04
      • 1970-01-01
      • 1970-01-01
      • 2016-06-19
      • 2021-12-17
      相关资源
      最近更新 更多