0

There's an array:

var arr = new int[] { 1, 1, 2, 6, 6, 7, 1, 1, 0 };

Is there a simple way to split it into arrays of the same values?

var arrs = new int[][] { 
            new int[] { 1, 1 },
            new int[] { 2 },
            new int[] { 6, 6 },
            new int[] { 7 }, 
            new int[] { 1, 1 }, 
            new int[] { 0 } };

I would prefer a linq solution but couldn't find it at the first time.

3

5 Answers 5

5

I would write an extension method for this:

public static class SOExtensions
{
    public static IEnumerable<IEnumerable<T>> GroupSequenceWhile<T>(this IEnumerable<T> seq, Func<T, T, bool> condition) 
    {
        List<T> list = new List<T>();
        using (var en = seq.GetEnumerator())
        {
            if (en.MoveNext())
            {
                var prev = en.Current;
                list.Add(en.Current);

                while (en.MoveNext())
                {
                    if (condition(prev, en.Current))
                    {
                        list.Add(en.Current);
                    }
                    else
                    {
                        yield return list;
                        list = new List<T>();
                        list.Add(en.Current);
                    }
                    prev = en.Current;
                }

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

and use it as

var arr = new int[] { 1, 1, 2, 6, 6, 7, 1, 1, 0 };
var result = arr.GroupSequenceWhile((x, y) => x == y).ToList();
Sign up to request clarification or add additional context in comments.

Comments

3
var grouped = arr.GroupBy(x => x).Select(x => x.ToArray())

Didn't notice you were after neighbouring groups initially, the following should work for that

var arr = new[] { 1, 1, 2, 6, 6, 7, 1, 1, 0 };
var groups = new List<int[]>();
for (int i = 0; i < arr.Length; i++)
{
    var neighours = arr.Skip(i).TakeWhile(x => arr[i] == x).ToArray();
    groups.Add(neighours);
    i += neighours.Length-1;
}

Live example

Comments

2

This will do the trick:

var arrs = arr.Select((x, index) =>
    {
        var ar = arr.Skip(index)
            .TakeWhile(a => a == x)
            .ToArray();
        return ar;
    }).Where((x, index) => index == 0 || arr[index - 1] != arr[index]).ToArray();

Basically this will generate an array for each sequence item with a length of 1 or greater and will only choose the arrays which correspond to an item in the original sequence which is either the first element or an element that differs from its predecessor.

Comments

1

You can try this:

int index = 0;
var result = arr.Select(number =>
            {
                var ar = arr.Skip(index)
                    .TakeWhile(a => a == number)
                    .ToArray();
                index += ar.Length;
                return ar;
            }).Where(x => x.Any()).ToArray();

1 Comment

Pretty nice. One thing I don't like is a mutation of the index variable.
1

An extension method like the answer by @L.B but a little bit more functional oriented:

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> source, Func<T, T, bool> func)
{
    var firstElement = source.FirstOrDefault();

    return firstElement == null ? Enumerable.Empty<IEnumerable<T>>() : source.Skip(1).Aggregate(new
    {
        current = Tuple.Create(firstElement, ImmutableList<T>.Empty.Add(firstElement)),
        results = ImmutableList<ImmutableList<T>>.Empty,
    }, (acc, x) =>
        func(acc.current.Item1, x)
        ? new { current = Tuple.Create(x, acc.current.Item2.Add(x)), results = acc.results }
        : new { current = Tuple.Create(x, ImmutableList<T>.Empty.Add(x)), results = acc.results.Add(acc.current.Item2) }, 
        x => x.results.Add(x.current.Item2).Select(r => r));
}

Note that the extension method uses the Microsoft Immutable Collections library. The library can be downloaded through NuGet.

Usage:

var arr = new int[] { 1, 1, 2, 6, 6, 7, 1, 1, 0 };
var result = arr.GroupWhile((prev, current) => prev == current);
var printFormattedResult = result.Select((x, i) => Tuple.Create(i, string.Join(",", x)));

foreach (var array in printFormattedResult)
    Console.WriteLine("Array {0} = {1}", array.Item1, array.Item2);

Output:

Array 0 = 1,1
Array 1 = 2
Array 2 = 6,6
Array 3 = 7
Array 4 = 1,1
Array 5 = 0

Benchmark

Just for the sake of fun, I tried to benchmark the answers.

I used the following code:

var rnd = new Random();
var arr = Enumerable.Range(0, 100000).Select(x => rnd.Next(10)).ToArray();
var time = Stopwatch.StartNew();

var result = <answer>.ToArray();

Console.WriteLine(t.ElapsedMilliseconds);

And got the following results:

-------------------------------------
| Solution  Time(ms)    Complexity  |
------------------------------------|
| L.B       | 3ms       | O(n)      |
|-----------------------------------|
|´ebb       | 41ms      | O(n)      |
|-----------------------------------|
| James     | 137ms     | O(n^2)    |
|-----------------------------------|
| Robert S. | 155ms     | O(n^2)    |
|-----------------------------------|
| Selman22  | 155ms     | O(n^2)    |
-------------------------------------

The slight time overhead from my solution (the 41ms) is due to using immutable collections. Adding an item to ex. List<T> would modify the List<T> object. - Adding an item to ImmutableList<T> clones the current elements in it, and adds them to a new ImmutableList<T> along with the new item (which results in a slight overhead).

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.