将大的IEnumerable分成更小的IEnumerable项目的修复量

问题描述:

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

假设列表{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,但我无法确定如何编写该文件。

+0

http://*.com/questions/1349491/how-can-i-split-an-ienumerablestring-into-groups-of-ienumerablestring – diceguyd30 2011-03-02 19:34:51

+0

的[拆分名单可能重复与子列表LINQ](http://*.com/questions/419019/split-list-into-sublists-with-linq) – nawfal 2013-02-18 10:28:43

尝试这样:

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

它通过划分项目到根据他们在原来的列表索引组。

+0

读你的代码引发了一个想法。 'int index = 0; var groups = items.GroupBy(x => index ++/5);'这个工作。谢谢。像平常一样,它总是很简单。 – 2011-03-02 19:47:52

+3

@ Pierre-Alain:在LINQ查询中使用副作用通常会被忽视;它违背了LINQ的设计原则,这主要是功能性的。它在某种程度上会起作用......但是,例如,如果您评估“groups”两次,则会得到一些有趣的效果。 – 2011-03-02 19:50:28

+0

@Pierre传递给具有副作用的“GroupBy”的函数很难看。 Linq是关于功能的,因此是免费编程的副作用。 – CodesInChaos 2011-03-02 19:51:56

一个简单的可能性是使用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); 

由于乔恩在评论中提到的,虽然,像这样的一个简单的方法将重新评估nums(在这个例子中)每次都会影响性能(取决于集合的大小)。

+5

请注意,这将每次重新评估源 - 这在某些情况下很好(例如,内存中的集合),但不是全部(例如从大型日志文件中读取条目)。在可能的情况下,我尝试编写我的LINQ代码来仅评估一次源代码。 – 2011-03-02 19:35:34

+0

@Jon好点,谢谢。 – Donut 2011-03-02 19:39:04

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

您可以使用GroupBy,但不能偷懒 - 它必须积累全部结果才能返回任何内容。这可能对你很好,但值得注意。

+0

'第86行:bucket = null;'这是否确保每次都是不同的数组? – CodesInChaos 2011-03-02 20:18:32

+0

@CodeInChaos:'x => x'不会导致每个批次的一次分配 - 这意味着每个结果都是相同的数组引用(直到结束)。它实际上有点危险(例如'x.Batch(10).ToList()') – 2011-03-02 20:18:49

+0

@Jon我删除了原来的评论,因为我最初错过了'bucket = null'。 – CodesInChaos 2011-03-02 20:19:42

我只是做这样的事情:

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 

理由:

与其它方法相比这种多打电话给t ØSkipTake或大花式LINQ查询,上面是:

  • 更高效
  • 更多的功能明显(在我看来)
  • 实施更具可读性(同样,在我看来)
+1

如果您每次都要返回一份副本,为什么不直接为每个批次启动一个新列表并返回?调用ToArray的好处在哪里? – 2011-03-02 19:40:29

+0

@Jon:好问题。我想这没什么好的理由。 – 2011-03-02 19:50:39

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