In order to support an API that only accepts a specific amount of items (5 items), I want to transform a LINQ result into smaller groups of items that always contain that se
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
var result = new List<List<int>>();
while (list.Count != 0) {
result.Add(list.TakeWhile(x => x++ <= 5).ToList());
list.RemoveRange(0, list.Count < 5 ? list.Count : 5);
}
I'd just do something like this:
public static IEnumerable<IEnumerable<T>> TakeChunks<T>(this IEnumerable<T> source, int size)
{
// Typically you'd put argument validation in the method call and then
// implement it using a private method... I'll leave that to your
// imagination.
var list = new List<T>(size);
foreach (T item in source)
{
list.Add(item);
if (list.Count == size)
{
List<T> chunk = list;
list = new List<T>(size);
yield return chunk;
}
}
if (list.Count > 0)
{
yield return list;
}
}
Usage:
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
foreach (var chunk in list.TakeChunks(3))
{
Console.WriteLine(string.Join(", ", chunk));
}
Output:
1, 2, 3 4, 5, 6 7, 8, 9 10
Rationale:
Compared to other methods such multiple calls to Skip
and Take
or a big fancy LINQ query, the above is:
We have a Batch method in MoreLINQ. You need to be careful how you use it, as the batch that is passed to the selector each time is a reference to the same array - but it does work.
You can use GroupBy
, but that can't be lazy - it has to accumulate all the results before it can return anything. That may be okay for you, but it's worth being aware of.
One easy possibility is to use the Enumerable.Skip and Enumerable.Take methods, for example:
List<int> nums = new List<int>(){1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18};
var list1 = nums.Take(5);
var list2 = nums.Skip(5).Take(5);
var list3 = nums.Skip(10).Take(5);
var list4 = nums.Skip(15).Take(5);
As Jon mentioned in the comments though, a simple approach like this one will re-evaluate nums
(in this example) each time, which will impact performance (depending on the size of the collection).
Try something like this:
var result = items.Select((value, index) => new { Index = index, Value = value})
.GroupBy(x => x.Index / 5)
.Select(g => g.Select(x => x.Value).ToList())
.ToList();
It works by partitioning the items into groups based on their index in the original list.