6

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.

3
  • 5
    what is the crieteria on basis of which you split Commented Jan 16, 2014 at 9:46
  • @Co.Aden: As they wrote, they want contiguous ranges of numbers in individual subsequences. Commented Jan 16, 2014 at 9:49
  • @Јοеу then it must be {1,2,3,4},{7,8},{12,13,14} Commented Jan 16, 2014 at 9:50

4 Answers 4

3

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));
Sign up to request clarification or add additional context in comments.

Comments

3

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

1 Comment

It is omitting the last value of the input.
2

Assuming your input is in order, the following will work:

var grouped = input.Select((n, i) => new { n, d = n - i }).GroupBy(p => p.d, p => p.n);

It won't work if your input is e.g. { 1, 2, 3, 999, 5, 6, 7 }.

You'd get { { 1, 2, 3, 5, 6, 7 }, { 999 } }.

3 Comments

+1 for most concise/elegant given the constraint. However, easily remove the constraint with an OrderBy like so: input.OrderBy(n=>n).Select((n,i) => new { n, d=n-i }).GroupBy(p => p.d, p => p.n);
@msorens The OrderBy ruins the consecutive test, though. 1 2 3 8 1 2 3 should be { 1 2 3 } { 8 } { 1 2 3 } but gets jumbled if you sort it.
Good point; I was assuming the values were unique, but no reason they would have to be in the general case.
0

This works:

var results =
    listOfInt
        .Skip(1)
        .Aggregate(
            new List<List<int>>(new [] { listOfInt.Take(1).ToList() }),
            (a, x) =>
            {
                if (a.Last().Last() + 1 == x)
                {
                    a.Last().Add(x);
                }
                else
                {
                    a.Add(new List<int>(new [] { x }));
                }
                return a;
            });

I get this result:

results

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.