8

I have a List<string> and I want to take groups of 5 items from it. There are no keys or anything simple to group by...but it WILL always be a multiple of 5.

e.g.

{"A","16","49","FRED","AD","17","17","17","FRED","8","B","22","22","107","64"}

Take groups of:

"A","16","49","FRED","AD"
"17","17","17","FRED","8"
"B","22","22","107","64"

but I can't work out a simple way to do it!

Pretty sure it can be done with enumeration and Take(5)...

Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
BlueChippy
  • 5,935
  • 16
  • 81
  • 131
  • 1
    You can indeed use Take(5) and on the next iteration Skip(5).Take(5) and so on. – Kevin D Dec 11 '14 at 10:10
  • 2
    This works for small lists, but in general is a bad idea. It iterates over the collection once per group leading to quadratic running time [O(n^2)](http://stackoverflow.com/a/487300/141397). This applies to all the other answers here using `.Skip().Take()`. – 3dGrabber Dec 11 '14 at 10:29
  • 2
    http://stackoverflow.com/questions/419019/split-list-into-sublists-with-linq – Andrew Dec 11 '14 at 10:35
  • 1
    @3dGrabber: i have tested the `GroupBy` + the `Skip` approaches on a very large list(`for (int i = 0; i < 20; i++)list.AddRange(list);`. The `GroupBy` took 23 seconds whereas the `Skip` is still running after 10 minutes ;) – Tim Schmelter Dec 11 '14 at 10:42
  • @3dGrabber: do you mean this version? `... .GroupBy(x => x.index / 5)` – BlueChippy Dec 11 '14 at 10:45
  • 2
    I've stopped it after 20 minutes, i'm afraid that it would eat my PC otherwise. @BlueChippy: The morelinq `Batch` should be the most efficient apporach but the `GroupBy` is ok. 3dGrabbe was referring to the `Skip.Take` approaches which are really slow if the list is getting large. – Tim Schmelter Dec 11 '14 at 10:55

7 Answers7

10

You can use the integer division trick:

List<List<string>> groupsOf5 = list
    .Select((str, index) => new { str, index })
    .GroupBy(x => x.index / 5)
    .Select(g => g.Select(x => x.str).ToList())
    .ToList();
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • 1
    this will work but, 1) it will create unnecessary anon. objects, 2) it will iterate over the collection at least three times. – Selman Genç Dec 11 '14 at 10:30
  • 1
    http://stackoverflow.com/questions/419019/split-list-into-sublists-with-linq yeah, right – Andrew Dec 11 '14 at 10:35
8
 List<List<string>> result = new List<List<string>>();
 for(int i = 0; i < source.Count; i += 5 )
      result.Add(source.Skip(i).Take(5).ToList());

Like this?

Florian Schmidinger
  • 4,682
  • 2
  • 16
  • 28
  • 6
    This has terrible runtime performance, see my comment in the question. – 3dGrabber Dec 11 '14 at 10:32
  • you did not specify how long this list is nor what you want to do with this data. this solution is therefor a simple way how to do it. if you specify what you want to do with the data we can provide a more detailed and more performant solution – Florian Schmidinger Dec 11 '14 at 10:43
  • this is perfect – TDM Sep 08 '20 at 18:38
3

In common programming syntax:

     public List<List<string>> Split(List<string> items, int chunkSize = 5)
     {
         int chunkCount = items.Count/chunkSize;
         List<List<string>> result = new List<List<string>>(chunkCount);

         for (int i = 0; i < chunkCount; i++ )
         {
             result.Add(new List<string>(chunkSize));
             for (int j = i * chunkSize; j < (i + 1) * chunkSize; j++)
             {
                 result[i].Add(items[j]);
             }
         }

         return result;
     }

It's O((N/ChunkSize) x ChunkSize) = O(N), that is linear.

Andrew
  • 3,648
  • 1
  • 15
  • 29
  • throws index out of range exception – WhileTrueSleep Dec 11 '14 at 11:00
  • 1
    Best answer so far IMO. Since the chunk size is constant inside the loop you could use an array instead, which is faster and uses less memory. Or at least use the list constructor with 'initial capacity', so the list does not have to resize itself. – 3dGrabber Dec 11 '14 at 11:10
  • @3dGrabber, Unless you need something from the bench of functionalities that the List ships with, Array is faster. I'm not aware of the use cases, and List seams to be more universal in modern development. – Andrew Dec 11 '14 at 11:37
  • Always go for O(n), that trumps elegance every day, in every way. +1 for that! – code4life Dec 11 '14 at 12:04
  • Yeah, linear algorithms do scale. – Andrew Dec 11 '14 at 12:06
  • @Andrew: sure, array or list is debatable. But `result.Add(new List(chunkSize))` would still make sense. – 3dGrabber Dec 11 '14 at 12:20
  • Yes, there's internal array behind the scenes. Updated the answer. – Andrew Dec 11 '14 at 12:23
2

I recommend Batch method from MoreLINQ library:

var result = list.Batch(5).ToList();
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
1

Use Take() and Skip() to achieve this:

        List<string> list = new List<string>() { "A", "16", "49", "FRED", "AD", "17", "17", "17", "FRED", "8", "B", "22", "22", "107", "64" };

        List<List<string>> result = new List<List<string>>();
        for (int i = 0; i < list.Count / 5; i++)
        {
            result.Add(list.Skip(i * 5).Take(5).ToList());
        }
Adrian Nasui
  • 1,054
  • 9
  • 10
1

You can use this function:

public IEnumerable<string[]> GetChunk(string[] input, int size)
{
    int i = 0;
    while (input.Length > size * i)
    {
        yield return input.Skip(size * i).Take(size).ToArray();
        i++;
    }
}

it returns you chunks from your list

you can check it like

var list = new[]
{
    "A", "16", "49", "FRED", "AD", "17", "17", "17", "FRED", "8", "B", "22", "22", "107", "64"
};

foreach (var strings in GetChunk(list, 5))
{
    Console.WriteLine(strings.Length); 
}
Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
1

If you need performance or cannot use linq cause of your .net version here is a simple solution with O(n)

    private List<List<string>> SplitList(List<string> input, int size = 5)
    {
        var result = new List<List<string>>();
        for (int i = 0; i < input.Count; i++)
        {
            var partResult = new List<string>();
            while (true)
            {
                // save n items
                partResult.Add(input[i]);
                if ((i+1) % size == 0)
                {
                    break;
                }
                i++;
            }
            result.Add(partResult);
        }

        return result;
    }
WhileTrueSleep
  • 1,524
  • 1
  • 19
  • 32