LINQ query ― Data aggregation (Group Adjacent)

匿名 (未验证) 提交于 2019-12-03 02:05:01

问题:

Let's take a class called Cls:

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

Now, let's populate some collection with following elements:

Sequence Number      Value ========    ===== 1           9 2           9 3           15 4           15 5           15 6           30 7           9

What I need to do, is to enumerate over Sequence Numbers and check if the next element has the same value. If yes, values are aggregated and so, desired output is as following:

Sequence    Sequence Number      Number From        To          Value ========    ========    ===== 1           2           9 3           5           15 6           6           30 7           7           9

How can I perform this operation using LINQ query?

回答1:

You can use Linq's GroupBy in a modified version which groups only if the two items are adjacent, then it's easy as:

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

DEMO

Result:

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

This is the extension method to to group adjacent items:

public static IEnumerable> GroupAdjacent(         this IEnumerable source,         Func keySelector)     {         TKey last = default(TKey);         bool haveLast = false;         List list = new List();         foreach (TSource s in source)         {             TKey k = keySelector(s);             if (haveLast)             {                 if (!k.Equals(last))                 {                     yield return new GroupOfAdjacent(list, last);                     list = new List();                     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(list, last);     } }

and the class used:

public class GroupOfAdjacent : IEnumerable, IGrouping {     public TKey Key { get; set; }     private List GroupList { get; set; }     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()     {         return ((System.Collections.Generic.IEnumerable)this).GetEnumerator();     }     System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()     {         foreach (var s in GroupList)             yield return s;     }     public GroupOfAdjacent(List source, TKey key)     {         GroupList = source;         Key = key;     } }


回答2:

You can use this linq query

Demo

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


回答3:

MoreLinq provides this functionality out of the box

It's called GroupAdjacent and is implemented as extension method on IEnumerable:

Groups the adjacent elements of a sequence according to a specified key selector function.

enumerable.GroupAdjacent(e => e.Key)

There is even a Nuget "source" package that contains only that method, if you don't want to pull in an additional binary Nuget package.

The method returns an IEnumerable>, so its output can be processed in the same way output from GroupBy would be.



回答4:

You can do it like this:

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

The idea is to use Aggregate creatively: starting with a list consisting of a single Run, examine the content of the list we've got so far at each stage of aggregation (the if statement in the lambda). Depending on the last value, either continue the old run, or start a new one.

Here is a demo on ideone.



回答5:

I was able to accomplish it by creating a custom extension method.

static class Extensions {   internal static IEnumerable> GroupAdj(this IEnumerable 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);   } }

Here's the implementation:

static void Main() {   List items = new List {     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);   } }

And the expected output:

From  To    Value ===== ===== ===== 1     2     9 3     5     15 6     6     30 7     7     9


回答6:

Here is an implementation without any helper methods:

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

The idea is:

  • Keep track of your own grouping indicator, grp.
  • Join each item of the collection to the next item in the collection (via Skip(1) and Zip).
  • If the Values match, they are in the same group; otherwise, increment grp to signal the start of the next group.


回答7:

Untested dark magic follows. The imperative version seems like it would be easier in this case.

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


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!