11

I have a collection uni-dimensional like this:

[1,2,4,5.....n]

I would like to convert that collection in a bi-dimensional collection like this:

[[1,2,3],
[4,5,6],
...]

Basically I want to group or split if you want, the array in groups of 'n' members

I can do it with a foreach statement, but I am currently learning LINQ so instead of iterating through all elements and create a new array manually I would like to use the LINQ features (if applicable)

Is there any LINQ function to help me to accomplish this??

I was thinking in the GroupBy or SelectMany I do not know if they will help me though but they might

Any help will be truly appreciate it =) :**

Tilak
  • 30,108
  • 19
  • 83
  • 131
Rosana Huerta
  • 113
  • 1
  • 5
  • 1
    It would be possible to write such a query in pure LINQ in many different ways, but I wouldn't recommend doing that, it would be terribly inefficient. – Jeff Mercado May 31 '12 at 03:21
  • Does this answer your question? [Split List into Sublists with LINQ](https://stackoverflow.com/questions/419019/split-list-into-sublists-with-linq) – chwarr Dec 06 '22 at 00:21

8 Answers8

20

You can group by the index divided by the batch size, like this:

var batchSize = 3;
var batched = orig
    .Select((Value, Index) => new {Value, Index})
    .GroupBy(p => p.Index/batchSize)
    .Select(g => g.Select(p => p.Value).ToList());
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • I was thinking about how to do this with GroupBy, and couldn't figure how to consider the index... thanks. – Marc L. May 31 '12 at 14:58
5

Use MoreLinq.Batch

 var result = inputArray.Batch(n); // n -> batch size

Example

    var inputs = Enumerable.Range(1,10);

    var output = inputs.Batch(3);


    var outputAsArray = inputs.Batch(3).Select(x=>x.ToArray()).ToArray(); //If require as array
Tilak
  • 30,108
  • 19
  • 83
  • 131
  • 1
    +1 for the link to MoreLinq, thanks! I just want to notice that in some cases we have to embed MoreLinq as a source code instead of compiled dll, e.g. if we use LinqBridge. – Dmitrii Lobanov May 31 '12 at 03:31
  • This is interesting but i checked the source code and it is done with a `foreach` – Jupaol May 31 '12 at 03:32
  • Most of the methods use foreach with careful use of yield keyword. For any such requirement i first try to find out existing extensions in [MoreLinq](http://code.google.com/p/morelinq/), [LinqExtensions](http://linqlib.codeplex.com/), etc. If i dont find one, and it make sense, i create my own extension. – Tilak May 31 '12 at 03:51
3

You want Take() and Skip(). These methods will let you split an IEnumerable. Then you can use Concat() to slap them together again.

Jeremy Holovacs
  • 22,480
  • 33
  • 117
  • 254
2

It's not a pure LINQ but it's intended to be used with it:

public static class MyEnumerableExtensions
{
    public static IEnumerable<T[]> Split<T>(this IEnumerable<T> source, int size)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source can't be null.");
        }

        if (size == 0)
        {
            throw new ArgumentOutOfRangeException("Chunk size can't be 0.");
        }

        List<T> result = new List<T>(size);
        foreach (T x in source)
        {
            result.Add(x);
            if (result.Count == size)
            {
                yield return result.ToArray();
                result = new List<T>(size);
            }
        }
    }
}

It can be used from your code as:

private void Test()
{
    // Here's your original sequence
    IEnumerable<int> seq = new[] { 1, 2, 3, 4, 5, 6 };

    // Here's the result of splitting into chunks of some length 
    // (here's the chunks length equals 3). 
    // You can manipulate with this sequence further, 
    // like filtering or joining e.t.c.
    var splitted = seq.Split(3);
}
Dmitrii Lobanov
  • 4,897
  • 1
  • 33
  • 50
  • 1
    A semantic point: "splice" in English actually means "join together", whereas the method is actually pulling the array apart. – Marc L. May 31 '12 at 14:55
  • 1
    on last step if result.Count < size, last part of source will be lost – gabba Jun 05 '14 at 07:16
  • 1
    @gabba yeah, thanks! I assumed that the initial sequence would consist of `k*size` elements. I think it would be better to throw an exception if this is not true. – Dmitrii Lobanov Jun 06 '14 at 08:41
  • @DmitryLobanov, When in doubt, use "cleave" -- it means both "to split apart" and "to fuse together!" As the saying goes, "English doesn't borrow from other languages. English follows other languages down dark alleys, knocks them over, and rummages through their pockets for loose grammar." – Michael Blackburn Apr 17 '18 at 14:22
2

The sample below will split an array into groups of 4 items each.

int[] items = Enumerable.Range(1, 20).ToArray(); // Generate a test array to split
int[][] groupedItems = items
                         .Select((item, index) => index % 4 == 0 ? items.Skip(index).Take(4).ToArray() : null)
                         .Where(group => group != null)
                         .ToArray();
1

It's as simple as:

static class LinqExtensions
{
    public static IEnumerable<IEnumerable<T>> ToPages<T>(this IEnumerable<T> elements, int pageSize)
    {
        if (elements == null)
            throw new ArgumentNullException("elements");
        if (pageSize <= 0)
            throw new ArgumentOutOfRangeException("pageSize","Must be greater than 0!");

        int i = 0;
        var paged = elements.GroupBy(p => i++ / pageSize);
        return paged;
    }
}
ShloEmi
  • 1,851
  • 2
  • 20
  • 25
0

I based my solution of Jeremy Holovacs's answer and used Take() and Skip() to create subarrays.

        const int batchSize = 3;
        int[] array = new int[] { 1,2,4,5.....n};

        var subArrays = from index in Enumerable.Range(0, array.Length / batchSize + 1)
                      select array.Skip(index * batchSize).Take(batchSize);
DaemonFire
  • 573
  • 6
  • 13
0

Starting with .NET 6, there is the System.Linq.Enumerable.Chunk(this IEnumerable<TSource>, int size) extension method. It returns an IEnumerable<TSource[]> where each item is an array of size elements, except the last item, which could have fewer.

Code like this:

using System;
using System.Collections.Generic;
using System.Linq;

int[] input = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

IEnumerable<int[]> chunks = input.Chunk(3);

foreach (int[] chunk in chunks)
{
    foreach (int i in chunk)
    {
        Console.Write($"{i} ");
    }
    
    Console.WriteLine();
}

outputs

1 2 3 
4 5 6 
7 8 9 
10 
chwarr
  • 6,777
  • 1
  • 30
  • 57