如何在LINQ中按顺序分组?

时间:2012-06-29 11:58:11

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

给定序列:

["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'ish方法来实现这一点?这是我网域中反复出现的模式。

5 个答案:

答案 0 :(得分:3)

这是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));

答案 1 :(得分:2)

带有foreach

int.TryParse循环应该会有所帮助。来自LINQ的'GroupBy'在这方面没有多大帮助。

答案 2 :(得分: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);

然而,这看起来并不那么好,因为缺乏对不可变值的支持。另外,我现在无法测试。

答案 3 :(得分:2)

由于这是您域中的常见模式,因此请考虑对结果进行流式处理,而不是将它们全部收集到大型内存中对象中。

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

答案 4 :(得分:1)

这是我最终使用的内容。与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);