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

これはドメイン内の一般的なパターンであるため、結果をすべて大きなメモリ内オブジェクトに集めるのではなく、結果をストリーミングすることを検討してください。

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
于 2012-07-18T23:32:24.540 に答える
2

foreachloop withint.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);

ただし、不変値のサポートが不足しているため、これはあまり見栄えがよくありません。また、現在これをテストできませんでした。

于 2012-06-29T12:52:58.207 に答える
1

これが私が最終的に使用したものです。phgの回答とほぼ同じ構造です。

基本的に、これは以下を含む Tuple を維持する集計関数です。 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);
于 2012-07-04T22:15:21.600 に答える