Reputation: 23093
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 set amount of items.
Supposing the list {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}
I want to get three smaller lists of a maximum of 5 items each
{1, 2, 3, 4, 5}
{6, 7, 8, 9, 10}
{11, 12, 13, 14, 15}
{16, 17, 18}
How can I do that with LINQ? I'm assuming that it either involves Group
or Aggregate
, but I'm having trouble figuring how to write that.
Upvotes: 7
Views: 8864
Reputation: 1501163
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.
Upvotes: 1
Reputation: 128347
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:
Upvotes: 14
Reputation: 51644
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);
}
Upvotes: 0
Reputation: 112835
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).
Upvotes: 4
Reputation: 838416
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.
Upvotes: 17