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

时间:2011-03-02 19:29:53

标签: c# linq

为了支持只接受特定数量项目(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,但我无法确定如何编写它。

5 个答案:

答案 0 :(得分:17)

尝试这样的事情:

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

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

答案 1 :(得分:14)

我会做这样的事情:

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;
    }
}

用法:

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

输出:

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

理由:

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

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

答案 2 :(得分:4)

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

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