There is a List<int> object List<int> for example, let it be

 var src = new List<int> {0,1,3,5,6,7,9}; 

In the original list, duplicates are not possible, i.e. {0,1,1,3,5} not considered. The list is pre-sorted.

how to write a function that will return a list of objects to which consecutive digits will be, i.e. you need to get the following result:

 var result = new List<List<int>> { new List<int> {0,1}, new List<int> {3}, new List<int> {5,6,7}, new List<int> {9} }; 

it only comes to my mind to make an option in a for loop, but it seems to me that this can be done more elegantly using linq

  • see how the Linq order by group query works. Unfortunately I do not have a VS hand to set an example. - koks_rs

3 answers 3

In this case, you can use the Aggregate method, with an initial value for the battery.

 src.Aggregate(new List<List<int>>(), (acc, cur) => { //проверяем что мы, либо зашли в первый раз, либо разница между элементами больше 1. if (acc.Count == 0 || cur - acc.Last().Last() > 1) { //добавляем новый список с текущим элементом acc.Add(new List<int> { cur }); } else { //иначе добавляем в последний список acc.Last().Add(cur); } //возвращаем аккумулятор return acc; }); 

    This can be done using GroupBy grouping.

     var values = new List<int> { 0, 1, 3, 5, 6, 7, 9 }; int? prev = null, first = null; var grouped = values .GroupBy( v => v == prev + 1 ? first += prev++ * 0 : first = prev = v, (_, vs) => vs.ToList()) .ToList(); 

    There is a small problem with the fact that in C # there is no comma operation. If it were, instead of the strange formula, first += prev++ * 0 could be written simply (prev++, first) (that is, increment the first variable, return the second).

    Of course, this can be written in a civilized way:

     var grouped2 = values .GroupBy( v => { if (v == prev + 1) { prev++; return first; } else { return first = prev = v; } }, (_, vs) => vs.ToList()) .ToList(); 

    True it will not be so lokanichno. :)

    • after the solution provided by @Grundy it seems more complicated :) - Bald
    • one
      @ Bald56rus Well, there is a traditional cycle, just designed through Aggregate. And here is the group with the state. Of course, the traditional cycle is easier to perceive. If you ultimately need lists, then the method with GroupBy has no advantages. If enumerations are enough for you, then absolutely all logic fits into one line: GroupBy(v => v == prev + 1 ? first += prev++ * 0 : first = prev = v) (or GroupBy(v => v == prev + 1 ? (prev++; first) : first = prev = v) in the new version of the language). - Athari
    • But why multiply by 0? point blank I don't understand - Bald
    • @ Bald56rus The bottom line is that I need to push two operations into one expression: incrementing one variable and returning another. Multiplying by zero with, respectively, summing with zero allows you to use increment in the expression, while ignoring the value of the incremented variable. - Athari

    Another idea is to use a richer arsenal of LINQ operators.

    First of all, let's connect the package MoreLinq (package manager console → Install-Package morelinq ).

    To begin with, we need to find out for each of the elements whether it is necessary to finish the group on it. For this you need to consult with the previous item. This opportunity gives us the function Pairwise . Now, at the same time, we will “swallow” the first (or last) element, so we need to add it using the Prepend function.

    For the time being we get:

     src.Pairwise((prev, next) => new { val = next, sameGroup = next - prev == 1 }) .Prepend(new { val = src[0], sameGroup = true }) 

    Now, we need to group the sequence into chunks depending on the value of the sameGroup . I did not find such functionality out of the box, but it is easy to implement it myself:

     static class EnumerableExtensions { public static IEnumerable<IEnumerable<U>> SplitBy<T, U>( this IEnumerable<T> source, Func<T, bool> mayAppend, Func<T, U> selector) { var chunk = new List<U>(); foreach (var x in source) { if (!mayAppend(x)) { yield return chunk; chunk = new List<U>(); } chunk.Add(selector(x)); } if (chunk.Any()) yield return chunk; } } 

    Total:

     static void Main(string[] args) { var src = new List<int> { 0, 1, 3, 5, 6, 7, 9 }; var result = src.Pairwise((prev, next) => new { val = next, sameGroup = next - prev == 1 }) .Prepend(new { val = src[0], sameGroup = true }) .SplitBy(vs => vs.sameGroup, vs => vs.val); foreach (var seq in result) Console.WriteLine(string.Join(" ", seq)); } 

    displays

    0 1
    3
    5 6 7
    9

    • and Pairwise not the same as zip ? - Grundy
    • @Grundy: Well, almost. Zip works for two sequences, so a.Zip(b, f) == a.Zip(a.Skip(1), f) . And at the same time a will be calculated twice, but not with Pairwise. - VladD
    • well, yes :-) that's what I meant :-) when I tried with Zip - something didn’t grow together right away :-) and with Aggregate - fine :) - Grundy
    • I can understand the introduction of new methods, when their use simplifies the code, but when it complicates ... You even outdid my one-liner. :) - Athari
    • @Discord: Why does it complicate? The method itself is simple, and performs a small private task. - VladD