将一个大的IEnumerable划分为一个固定数量的项目的较小IEnumerable

为了支持只接受特定数量的项目(5项)的API,我想将LINQ结果转换为总是包含设定数量项目的较小项目组。

假设清单{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}

我想得到三个较小的列表,每个最多包含5个项目

{1, 2, 3, 4, 5}

{6, 7, 8, 9, 10}

{11, 12, 13, 14, 15}

{16, 17, 18}

我怎么能用LINQ做到这一点? 我假设它涉及GroupAggregate ,但我无法弄清楚如何编写它。

尝试这样的事情:

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

它的工作原理是根据原始列表中的索引将项目分组。

我会做这样的事情:

 public static IEnumerable> TakeChunks(this IEnumerable 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(size); foreach (T item in source) { list.Add(item); if (list.Count == size) { List chunk = list; list = new List(size); yield return chunk; } } if (list.Count > 0) { yield return list; } } 

用法:

 var list = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; foreach (var chunk in list.TakeChunks(3)) { Console.WriteLine(string.Join(", ", chunk)); } 

输出:

 1,2,3
 4,5,6
 7,8,9
 10

理由:

与其他方法相比,例如SkipTake多次调用或大型的LINQ查询,以上是:

  • 更高效
  • function更明显(在我看来)
  • 实施更具可读性(再次,在我看来)

一种简单的可能性是使用Enumerable.SkipEnumerable.Take方法,例如:

 List nums = new List(){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); 

正如Jon在评论中提到的那样,像这样的简单方法每次都会重新评估nums (在本例中),这会影响性能(取决于集合的大小)。

我们在MoreLINQ中有一个Batch方法。 您需要小心如何使用它,因为每次传递给选择器的批处理是对同一个数组的引用 – 但它确实有效。

您可以使用GroupBy ,但这不能是懒惰的 – 它必须在它返回任何内容之前累积所有结果。 这可能对你没问题,但值得注意。

 var list = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; var result = new List>(); while (list.Count != 0) { result.Add(list.TakeWhile(x => x++ <= 5).ToList()); list.RemoveRange(0, list.Count < 5 ? list.Count : 5); }