LINQ分区列表成8名成员列表

如何将一个列表(使用LINQ),并将其分成每8个条目分割原始列表的列表列表?

我想像这样会涉及跳过和/或采取,但我还是相当新的LINQ。

编辑:使用C#/ .Net 3.5

编辑2:这个问题措辞不同于其他“重复”的问题。 虽然问题是相似的,但是这个问题的答案是比较好的:“接受”的答案是非常可靠的(带有yield语句)以及Jon Skeet提出的使用MoreLinq的build议(这在“其他”问题中不被推荐。 )有时重复是好的,因为他们强迫重新检查一个问题。

使用以下扩展方法将input分解为子集

 public static class IEnumerableExtensions { public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max) { List<T> toReturn = new List<T>(max); foreach(var item in source) { toReturn.Add(item); if (toReturn.Count == max) { yield return toReturn; toReturn = new List<T>(max); } } if (toReturn.Any()) { yield return toReturn; } } } 

作为批处理方法,我们在MoreLINQ中只有这样一个方法:

 // As IEnumerable<IEnumerable<T>> var items = list.Batch(8); 

要么

 // As IEnumerable<List<T>> var items = list.Batch(8, seq => seq.ToList()); 

你最好使用像MoreLinq这样的库,但是如果你真的必须使用“纯LINQ”来完成这个工作,你可以使用GroupBy

 var sequence = new[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; var result = sequence.Select((x, i) => new {Group = i/8, Value = x}) .GroupBy(item => item.Group, g => g.Value) .Select(g => g.Where(x => true)); // result is: { {1,2,3,4,5,6,7,8}, {9,10,11,12,13,14,15,16} } 

基本上,我们使用Select()的版本来提供消耗的值的索引,我们将索引除以8以确定每个值属于哪个组。 然后我们用这个分组键对这个序列进行分组。 最后一个Select只是将IGrouping<>降低到一个IEnumerable<IEnumerable<T>> (因为IGrouping是一个IEnumerable ,所以不是必须的)。

通过分解例子中的常量8 ,并将其replace为指定的参数,将其变成可重用的方法已经很简单了。 这不一定是最优雅的解决scheme,它不再是一个懒惰的,stream媒体解决scheme…但它确实有效。

您也可以使用迭代器块( yield return )编写自己的扩展方法,这可以提供比GroupBy更好的性能和更less的内存。 这是MoreLinq的Batch()方法所做的。

 from b in Enumerable.Range(0,8) select items.Where((x,i) => (i % 8) == b); 

采取不会很有效率,因为它不会删除所采取的条目。

为什么不使用简单的循环:

 public IEnumerable<IList<T>> Partition<T>(this/* <-- see extension methods*/ IEnumerable<T> src,int num) { IEnumerator<T> enu=src.getEnumerator(); while(true) { List<T> result=new List<T>(num); for(int i=0;i<num;i++) { if(!enu.MoveNext()) { if(i>0)yield return result; yield break; } result.Add(enu.Current); } yield return result; } } 

这并不是什么原始的Linqdevise师的想法,但看看这种滥用GroupBy:

 public static IEnumerable<IEnumerable<T>> BatchBy<T>(this IEnumerable<T> items, int batchSize) { var count = 0; return items.GroupBy(x => (count++ / batchSize)).ToList(); } [TestMethod] public void BatchBy_breaks_a_list_into_chunks() { var values = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var batches = values.BatchBy(3); batches.Count().ShouldEqual(4); batches.First().Count().ShouldEqual(3); batches.Last().Count().ShouldEqual(1); } 

我认为这个问题赢得了“高尔夫”奖。 ToList非常重要,因为您希望在对输出进行任何操作之前确保已经执行了分组。 如果你删除ToList ,你会得到一些奇怪的副作用。

Mel提供了最简单的解决scheme:

 public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize) { int i = 0; return items.GroupBy(x => i++ / partitionSize).ToArray(); } 

简洁但较慢。 上面的方法将一个IEnumerable分割成所需固定大小的块,其中块的总数不重要。 要将一个IEnumerable分成N个相等大小或接近相等大小的块,可以这样做:

 public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, int numOfParts) { int i = 0; return items.GroupBy(x => i++ % numOfParts); } 

为了加快速度,一个简单的方法就是:

 public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize) { if (partitionSize <= 0) throw new ArgumentOutOfRangeException("partitionSize"); int innerListCounter = 0; int numberOfPackets = 0; foreach (var item in items) { innerListCounter++; if (innerListCounter == partitionSize) { yield return items.Skip(numberOfPackets * partitionSize).Take(partitionSize); innerListCounter = 0; numberOfPackets++; } } if (innerListCounter > 0) yield return items.Skip(numberOfPackets * partitionSize); } 

这比现在在这个星球上的任何东西都要快:) 这里的Split操作的等效方法