20

と呼ばれるクラスを取りましょうCls

public class Cls
{
    public int SequenceNumber { get; set; }
    public int Value { get; set; }
}

それでは、いくつかのコレクションに次の要素を入力してみましょう。

順序
数値値
======== =====
1 9
2 9
3 15
4 15
5 15
6 30
7 9

私がする必要があるのは、シーケンス番号を列挙し、次の要素が同じ値を持っているかどうかを確認することです。はいの場合、値は集計されるため、必要な出力は次のようになります。

シーケンスシーケンス
番号番号
From To Value
======== ======== =====
1 2 9
3 5 15
6 6 30
7 7 9

LINQクエリを使用してこの操作を実行するにはどうすればよいですか?

4

7 に答える 7

18

LinqGroupByは、2つのアイテムが隣接している場合にのみグループ化される修正バージョンで使用でき、次のように簡単に実行できます。

var result = classes
    .GroupAdjacent(c => c.Value)
    .Select(g => new { 
        SequenceNumFrom = g.Min(c => c.SequenceNumber),
        SequenceNumTo = g.Max(c => c.SequenceNumber),  
        Value = g.Key
    });

foreach (var x in result)
    Console.WriteLine("SequenceNumFrom:{0} SequenceNumTo:{1} Value:{2}", x.SequenceNumFrom, x.SequenceNumTo, x.Value);

デモ

結果:

SequenceNumFrom:1  SequenceNumTo:2  Value:9
SequenceNumFrom:3  SequenceNumTo:5  Value:15
SequenceNumFrom:6  SequenceNumTo:6  Value:30
SequenceNumFrom:7  SequenceNumTo:7  Value:9

これは、隣接するアイテムをグループ化するための拡張方法です。

public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector)
    {
        TKey last = default(TKey);
        bool haveLast = false;
        List<TSource> list = new List<TSource>();
        foreach (TSource s in source)
        {
            TKey k = keySelector(s);
            if (haveLast)
            {
                if (!k.Equals(last))
                {
                    yield return new GroupOfAdjacent<TSource, TKey>(list, last);
                    list = new List<TSource>();
                    list.Add(s);
                    last = k;
                }
                else
                {
                    list.Add(s);
                    last = k;
                }
            }
            else
            {
                list.Add(s);
                last = k;
                haveLast = true;
            }
        }
        if (haveLast)
            yield return new GroupOfAdjacent<TSource, TKey>(list, last);
    }
}

および使用されるクラス:

public class GroupOfAdjacent<TSource, TKey> : IEnumerable<TSource>, IGrouping<TKey, TSource>
{
    public TKey Key { get; set; }
    private List<TSource> GroupList { get; set; }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((System.Collections.Generic.IEnumerable<TSource>)this).GetEnumerator();
    }
    System.Collections.Generic.IEnumerator<TSource> System.Collections.Generic.IEnumerable<TSource>.GetEnumerator()
    {
        foreach (var s in GroupList)
            yield return s;
    }
    public GroupOfAdjacent(List<TSource> source, TKey key)
    {
        GroupList = source;
        Key = key;
    }
}
于 2013-02-14T16:32:51.847 に答える
3

このlinqクエリを使用できます

デモ

var values = (new[] { 9, 9, 15, 15, 15, 30, 9 }).Select((x, i) => new { x, i });

var query = from v in values
            let firstNonValue = values.Where(v2 => v2.i >= v.i && v2.x != v.x).FirstOrDefault()
            let grouping = firstNonValue == null ? int.MaxValue : firstNonValue.i
            group v by grouping into v
            select new
            {
              From = v.Min(y => y.i) + 1,
              To = v.Max(y => y.i) + 1,
              Value = v.Min(y => y.x)
            };
于 2013-02-14T17:17:29.117 に答える
3

MoreLinqは、この機能をすぐに提供します

これは呼び出され、次のGroupAdjacent拡張メソッドとして実装されIEnumerableます。

指定されたキーセレクター機能に従って、シーケンスの隣接する要素をグループ化します。

enumerable.GroupAdjacent(e => e.Key)

追加のバイナリNugetパッケージをプルしたくない場合は、そのメソッドのみを含むNuget「ソース」パッケージもあります。

このメソッドはを返すIEnumerable<IGrouping<TKey, TValue>>ため、その出力はからの出力と同じ方法で処理できGroupByます。

于 2015-04-24T06:12:28.970 に答える
2

あなたはこのようにそれを行うことができます:

var all = new [] {
    new Cls(1, 9)
,   new Cls(2, 9)
,   new Cls(3, 15)
,   new Cls(4, 15)
,   new Cls(5, 15)
,   new Cls(6, 30)
,   new Cls(7, 9)
};
var f = all.First();
var res = all.Skip(1).Aggregate(
    new List<Run> {new Run {From = f.SequenceNumber, To = f.SequenceNumber, Value = f.Value} }
,   (p, v) => {
    if (v.Value == p.Last().Value) {
        p.Last().To = v.SequenceNumber;
    } else {
        p.Add(new Run {From = v.SequenceNumber, To = v.SequenceNumber, Value = v.Value});
    }
    return p;
});
foreach (var r in res) {
    Console.WriteLine("{0} - {1} : {2}", r.From, r.To, r.Value);
}

アイデアはAggregate創造的に使用することです。単一のリストから始めて、集計の各段階(ラムダのステートメント)でRunこれまでに取得したリストの内容を調べます。if最後の値に応じて、古い実行を続行するか、新しい実行を開始します。

これがideoneのデモです。

于 2013-02-14T16:32:31.887 に答える
2

カスタム拡張メソッドを作成することでそれを達成することができました。

static class Extensions {
  internal static IEnumerable<Tuple<int, int, int>> GroupAdj(this IEnumerable<Cls> enumerable) {
    Cls start = null;
    Cls end = null;
    int value = Int32.MinValue;

    foreach (Cls cls in enumerable) {
      if (start == null) {
        start = cls;
        end = cls;
        continue;
      }

      if (start.Value == cls.Value) {
        end = cls;
        continue;
      }

      yield return Tuple.Create(start.SequenceNumber, end.SequenceNumber, start.Value);
      start = cls;
      end = cls;
    }

    yield return Tuple.Create(start.SequenceNumber, end.SequenceNumber, start.Value);
  }
}

実装は次のとおりです。

static void Main() {
  List<Cls> items = new List<Cls> {
    new Cls { SequenceNumber = 1, Value = 9 },
    new Cls { SequenceNumber = 2, Value = 9 },
    new Cls { SequenceNumber = 3, Value = 15 },
    new Cls { SequenceNumber = 4, Value = 15 },
    new Cls { SequenceNumber = 5, Value = 15 },
    new Cls { SequenceNumber = 6, Value = 30 },
    new Cls { SequenceNumber = 7, Value = 9 }
  };

  Console.WriteLine("From  To    Value");
  Console.WriteLine("===== ===== =====");
  foreach (var item in items.OrderBy(i => i.SequenceNumber).GroupAdj()) {
    Console.WriteLine("{0,-5} {1,-5} {2,-5}", item.Item1, item.Item2, item.Item3);
  }
}

そして期待される出力:

From  To    Value
===== ===== =====
1     2     9
3     5     15
6     6     30
7     7     9
于 2013-02-14T16:38:42.023 に答える
2

ヘルパーメソッドを使用しない実装は次のとおりです。

var grp = 0;
var results =
from i
in
input.Zip(
    input.Skip(1).Concat(new [] {input.Last ()}),
    (n1, n2) => Tuple.Create(
        n1, (n2.Value == n1.Value) ? grp : grp++
    )
)
group i by i.Item2 into gp
select new {SequenceNumFrom = gp.Min(x => x.Item1.SequenceNumber),SequenceNumTo = gp.Max(x => x.Item1.SequenceNumber), Value = gp.Min(x => x.Item1.Value)};

アイデアは次のとおりです。

  • 独自のグループ化インジケーター、grpを追跡します。
  • コレクションの各アイテムをコレクション内の次のアイテムに結合します(Skip(1)およびZipを使用)。
  • 値が一致する場合、それらは同じグループにあります。それ以外の場合は、grpをインクリメントして、次のグループの開始を通知します。
于 2013-02-14T17:24:04.847 に答える
1

テストされていないダークマジックが続きます。この場合、命令型の方が簡単なようです。

IEnumerable<Cls> data = ...;
var query = data
    .GroupBy(x => x.Value)
    .Select(g => new
    {
        Value = g.Key,
        Sequences = g
            .OrderBy(x => x.SequenceNumber)
            .Select((x,i) => new
            {
                x.SequenceNumber,
                OffsetSequenceNumber = x.SequenceNumber - i
            })
            .GroupBy(x => x.OffsetSequenceNumber)
            .Select(g => g
                .Select(x => x.SequenceNumber)
                .OrderBy(x => x)
                .ToList())
            .ToList()
    })
    .SelectMany(x => x.Sequences
        .Select(s => new { First = s.First(), Last = s.Last(), x.Value }))
    .OrderBy(x => x.First)
    .ToList();
于 2013-02-14T16:51:18.960 に答える