1

给定序列:

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

4

5 回答 5

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));
于 2012-06-29T12:34:32.217 回答
2

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

于 2012-06-29T12:02:58.413 回答
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);

However, this doesn't look so nice, since there's lacking support for immutable values. Also, I couldn't test this right now.

于 2012-06-29T12:52:58.207 回答
2

Since this a common pattern in your domain, consider streaming the results instead of gathering them all into a large in-memory object.

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

Sample usage:

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

output:

1: A B C
2: F K L
5: 
6: P I E
于 2012-07-18T23:32:24.540 回答
1

Here's what I ended up using. Pretty much the same structure as phg's answer.

Basically, it is an aggregate function that maintains a Tuple containing: 1: the accummulated data. 2: state of the parser.

The aggregating function does an if-else to check if currently examined item is a group header or a regular item. Based on this, it updates the datastore (last part of the tuple) and/or changes the parser state (first part of the tuple).

In my case, the parser state is the currently active list (that upcoming items shall be inserted into).

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);
于 2012-07-04T22:15:21.600 回答