Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Split array with LINQ

Tags:

c#

list

linq

Assuming I have a list

var listOfInt = new List<int> {1, 2, 3, 4, 7, 8, 12, 13, 14}

How can I use LINQ to obtain a list of lists as follows:

{{1, 2, 3, 4}, {7, 8}, {12, 13, 14}}

So, i have to take the consecutive values and group them into lists.

like image 381
mihai.b Avatar asked Jan 16 '14 09:01

mihai.b


2 Answers

You can create extension method (I omitted source check here) which will iterate source and create groups of consecutive items. If next item in source is not consecutive, then current group is yielded:

public static IEnumerable<List<int>> ToConsecutiveGroups(
    this IEnumerable<int> source)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
        {
            yield break;
        }
        else
        {                    
            int current = iterator.Current;
            List<int> group = new List<int> { current };

            while (iterator.MoveNext())
            {
                int next = iterator.Current;
                if (next < current || current + 1 < next)
                {
                    yield return group;
                    group = new List<int>();                            
                }

                current = next;
                group.Add(current);
            }

            if (group.Any())
                yield return group;
        }                
    }
}

Usage is simple:

var listOfInt = new List<int> { 1, 2, 3, 4, 7, 8, 12, 13, 14 };
var groups = listOfInt.ToConsecutiveGroups();

Result:

[
  [ 1, 2, 3, 4 ],
  [ 7, 8 ],
  [ 12, 13, 14 ]
]

UPDATE: Here is generic version of this extension method, which accepts predicate for verifying if two values should be considered consecutive:

public static IEnumerable<List<T>> ToConsecutiveGroups<T>(
    this IEnumerable<T> source, Func<T,T, bool> isConsequtive)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
        {
            yield break;
        }
        else
        {                    
            T current = iterator.Current;
            List<T> group = new List<T> { current };

            while (iterator.MoveNext())
            {
                T next = iterator.Current;
                if (!isConsequtive(current, next))
                {
                    yield return group;
                    group = new List<T>();                            
                }

                current = next;
                group.Add(current);
            }

            if (group.Any())
                yield return group;
        }                
    }
}

Usage is simple:

var result = listOfInt.ToConsecutiveGroups((x,y) => (x == y) || (x == y - 1));
like image 51
Sergey Berezovskiy Avatar answered Sep 22 '22 09:09

Sergey Berezovskiy


This works for both sorted and unsorted lists:

var listOfInt = new List<int> { 1, 2, 3, 4, 7, 8, 12, 13 };
int index = 0;
var result = listOfInt.Zip(listOfInt
                            .Concat(listOfInt.Reverse<int>().Take(1))
                            .Skip(1), 
                            (v1, v2) => 
                            new 
                            { 
                                V = v1, 
                                G = (v2 - v1) != 1 ? index++ : index
                            })
                        .GroupBy(x => x.G, x => x.V, (k, l) => l.ToList())
                        .ToList();

External index is building an index of consecutive groups that have value difference of 1. Then you can simply GroupBy with respect to this index.

To clarify solution, here is how this collection looks without grouping (GroupBy commented):

enter image description here

like image 33
Konrad Kokosa Avatar answered Sep 26 '22 09:09

Konrad Kokosa