shadeglare
shadeglare

Reputation: 7536

Split array into array of arrays

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.

Upvotes: 0

Views: 902

Answers (5)

ebb
ebb

Reputation: 9377

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

Upvotes: 1

Robert S.
Robert S.

Reputation: 2042

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.

Upvotes: 2

Selman Gen&#231;
Selman Gen&#231;

Reputation: 101681

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

Upvotes: 1

L.B
L.B

Reputation: 116138

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

Upvotes: 5

James
James

Reputation: 82096

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

Upvotes: 3

Related Questions