0

现有的 Linq 函数有没有办法从项目列表中创建任意大小的组?

例如:

[1,2,3,4,5,6,7]

当执行 list.Group(3) 之类的操作时,会产生一个 IEnumerable 的 IEnumberable,如下所示。

[[1,2,3],[4,5,6],[7]]
4

4 回答 4

5

我们在MoreLINQ 中将其作为Batch.

var batch = source.Batch(3);

正如您从代码中看到的那样,使用“标准”LINQ 运算符高效实现并不是一件容易的事,但它显然是可行的。请注意,它涉及缓冲输入,因为结果序列需要是独立的。

如果您确实想仅使用标准运算符来执行此操作,那么效率较低的实现将是:

// Assume "size" is the batch size
var query = source.Select((value, index) => new { value, index })
                  .GroupBy(pair => pair.index / size, pair => pair.value);

编辑:只是为了说明为什么这比约翰费舍尔的答案更安全,这里有一个简短但完整的程序来显示差异:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main(String[] args)
    {
        int[] source = { 1, 2, 3, 4, 5, 6, 7 };

        var skeet = SkeetAnswer(source, 3);
        var fisher = FisherAnswer(source, 3);

        Console.WriteLine("Size of the first element of Skeet's solution:");
        Console.WriteLine(skeet.First().Count());
        Console.WriteLine(skeet.First().Count());
        Console.WriteLine(skeet.First().Count());

        Console.WriteLine("Size of the first element of Fisher's solution:");
        Console.WriteLine(fisher.First().Count());
        Console.WriteLine(fisher.First().Count());
        Console.WriteLine(fisher.First().Count());
    }

    static IEnumerable<IEnumerable<int>> SkeetAnswer(IEnumerable<int> source,
                                                     int size)
    {
        return source.Select((value, index) => new { value, index })
                     .GroupBy(pair => pair.index / size, pair => pair.value);
    }

    static IEnumerable<IEnumerable<int>> FisherAnswer(IEnumerable<int> source,
                                                      int size)
    {
        int index = 0;
        return source.GroupBy(x => (index++ / size));
    }
}

结果:

Size of the first element of Skeet's solution:
3
3
3
Size of the first element of Fisher's solution:
3
2
1

虽然您可以在最后调用ToList(),但此时您已经失去了该方法的效率收益 - 基本上,John 的方法避免为每个成员创建一个匿名类型的实例。这可以通过使用等效于 的值类型来缓解Tuple<,>,这样就不会创建更多的对象,只是包装在另一个值中的值对。进行投影然后分组所需的时间仍然略有增加。

这很好地说明了为什么在 LINQ 查询中产生副作用(在这种情况下,修改捕获的变量index)是一个坏主意。

另一种选择是编写一个实现,该实现为键投影GroupBy提供每个元素的 。index这就是 LINQ 的优点 - 有很多选择!

于 2011-11-18T17:39:27.190 回答
0

我不认为有任何内置方法可以做到这一点,但实现起来并不难。您所指的 group 方法更像是一个 SQL 组。您所说的通常称为分块。

于 2011-11-18T17:40:08.867 回答
0

这应该这样做。

//var items = new int[] { 1, 2, 3, 4, 5, 6, 7 };
int index = 0;
var grouped = items.GroupBy(x => (index++ / 3));

无需为其他答案中的额外选择步骤而烦恼。仅仅创建具有额外索引值的一次性对象会浪费内存和时间。

编辑

正如 Jon Skeet 所提到的,迭代两次分组可能会导致问题(当被分组的项目没有完全划分为组大小时,在本例中为 3)。

为了缓解这种情况,您可以使用他的建议,或者您可以在结果上调用ToList()。(从技术上讲,您也可以在每次迭代组时将索引重置为零,但这是一种令人讨厌的代码气味。)

于 2011-11-18T17:55:10.800 回答
0

您可以将此代码重构为扩展方法并将其用于 List:

int i = 0;
var result = list
    .Select(p => new { Counter = i++, Item = p })
    .Select(p => new { Group = p.Counter / 3, Item = p.Item })
    .GroupBy(p => p.Group)
    .Select(p=>p.Select(q=>q.Item))
    .ToList();
于 2011-11-18T18:06:36.273 回答