用LINQ将一个集合拆分成`n`部分?

有没有一种很好的方式来将一个集合分成n部分与LINQ? 当然不一定均匀。

也就是说,我想把集合分成子集合,每个子集合都包含一个元素的子集,最后一个集合可以被破坏。

一个纯粹的linq和最简单的解决scheme如下所示。

 static class LinqExtensions { public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts) { int i = 0; var splits = from item in list group item by i++ % parts into part select part.AsEnumerable(); return splits; } } 

编辑:好吧,看起来我误解了这个问题。 我把它看作“长度为n的块”而不是“n块”。 卫生署! 考虑删除答案…

(原文答案)

我不相信有一个内置的分区方式,虽然我打算写在我的LINQ到对象的一组补充。 Marc Gravell 在这里有一个实现,虽然我可能会修改它返回一个只读视图:

 public static IEnumerable<IEnumerable<T>> Partition<T> (this IEnumerable<T> source, int size) { T[] array = null; int count = 0; foreach (T item in source) { if (array == null) { array = new T[size]; } array[count] = item; count++; if (count == size) { yield return new ReadOnlyCollection<T>(array); array = null; count = 0; } } if (array != null) { Array.Resize(ref array, count); yield return new ReadOnlyCollection<T>(array); } } 
 static class LinqExtensions { public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts) { return list.Select((item, index) => new {index, item}) .GroupBy(x => x.index % parts) .Select(x => x.Select(y => y.item)); } } 

好吧,我会把我的帽子扔在戒指里。 我的algorithm的优点:

  1. 没有昂贵的乘法,除法或模数运算符
  2. 所有的操作是O(1)(见下面的注释)
  3. 适用于IEnumerable <>源(不需要Count属性)
  4. 简单

代码:

 public static IEnumerable<IEnumerable<T>> Section<T>(this IEnumerable<T> source, int length) { if (length <= 0) throw new ArgumentOutOfRangeException("length"); var section = new List<T>(length); foreach (var item in source) { section.Add(item); if (section.Count == length) { yield return section.AsReadOnly(); section = new List<T>(length); } } if (section.Count > 0) yield return section.AsReadOnly(); } 

正如在下面的评论中指出的那样,这种方法实际上并没有解决要求固定数量的段大致相等的原始问题。 这就是说,你仍然可以通过这种方式来使用我的方法来解决原来的问题:

 myEnum.Section(myEnum.Count() / number_of_sections + 1) 

当以这种方式使用时,由于Count()操作是O(N),所以方法不再是O(1)。

这与被接受的答案是一样的,但更简单的表示:

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

上面的方法将一个IEnumerable<T>分成N个大小相等或接近相等的块。

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

上面的方法将一个IEnumerable<T>分割成所需的固定大小的块,其总块数不重要 – 这不是问题所在。

除了速度较慢之外, Split方法的问题在于,它会对输出进行加扰,因为在每个位置的N的倍数的基础上进行分组,或者换句话说,大块在原始的顺序。

这里几乎所有的答案都不能维持秩序,或者是分裂而不是分裂,或者显然是错误的。 尝试这是更快,保留秩序,但律'更详细:

 public static IEnumerable<IEnumerable<T>> Split<T>(this ICollection<T> items, int numberOfChunks) { if (numberOfChunks <= 0 || numberOfChunks > items.Count) throw new ArgumentOutOfRangeException("numberOfChunks"); int sizePerPacket = items.Count / numberOfChunks; int extra = items.Count % numberOfChunks; for (int i = 0; i < numberOfChunks - extra; i++) yield return items.Skip(i * sizePerPacket).Take(sizePerPacket); int alreadyReturnedCount = (numberOfChunks - extra) * sizePerPacket; int toReturnCount = extra == 0 ? 0 : (items.Count - numberOfChunks) / extra + 1; for (int i = 0; i < extra; i++) yield return items.Skip(alreadyReturnedCount + i * toReturnCount).Take(toReturnCount); } 

这里的Partition操作的等效方法

我一直在使用我早些时候发布的Partition函数。 唯一不好的地方就是没有完全stream式传输。 如果在序列中使用less量元素,这不是问题。 当我开始使用我的序列中的100.000个元素时,我需要一个新的解决scheme。

下面的解决scheme是更复杂(和更多的代码!),但它是非常有效的。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; namespace LuvDaSun.Linq { public static class EnumerableExtensions { public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int partitionSize) { /* return enumerable .Select((item, index) => new { Item = item, Index = index, }) .GroupBy(item => item.Index / partitionSize) .Select(group => group.Select(item => item.Item) ) ; */ return new PartitioningEnumerable<T>(enumerable, partitionSize); } } class PartitioningEnumerable<T> : IEnumerable<IEnumerable<T>> { IEnumerable<T> _enumerable; int _partitionSize; public PartitioningEnumerable(IEnumerable<T> enumerable, int partitionSize) { _enumerable = enumerable; _partitionSize = partitionSize; } public IEnumerator<IEnumerable<T>> GetEnumerator() { return new PartitioningEnumerator<T>(_enumerable.GetEnumerator(), _partitionSize); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } class PartitioningEnumerator<T> : IEnumerator<IEnumerable<T>> { IEnumerator<T> _enumerator; int _partitionSize; public PartitioningEnumerator(IEnumerator<T> enumerator, int partitionSize) { _enumerator = enumerator; _partitionSize = partitionSize; } public void Dispose() { _enumerator.Dispose(); } IEnumerable<T> _current; public IEnumerable<T> Current { get { return _current; } } object IEnumerator.Current { get { return _current; } } public void Reset() { _current = null; _enumerator.Reset(); } public bool MoveNext() { bool result; if (_enumerator.MoveNext()) { _current = new PartitionEnumerable<T>(_enumerator, _partitionSize); result = true; } else { _current = null; result = false; } return result; } } class PartitionEnumerable<T> : IEnumerable<T> { IEnumerator<T> _enumerator; int _partitionSize; public PartitionEnumerable(IEnumerator<T> enumerator, int partitionSize) { _enumerator = enumerator; _partitionSize = partitionSize; } public IEnumerator<T> GetEnumerator() { return new PartitionEnumerator<T>(_enumerator, _partitionSize); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } class PartitionEnumerator<T> : IEnumerator<T> { IEnumerator<T> _enumerator; int _partitionSize; int _count; public PartitionEnumerator(IEnumerator<T> enumerator, int partitionSize) { _enumerator = enumerator; _partitionSize = partitionSize; } public void Dispose() { } public T Current { get { return _enumerator.Current; } } object IEnumerator.Current { get { return _enumerator.Current; } } public void Reset() { if (_count > 0) throw new InvalidOperationException(); } public bool MoveNext() { bool result; if (_count < _partitionSize) { if (_count > 0) { result = _enumerator.MoveNext(); } else { result = true; } _count++; } else { result = false; } return result; } } } 

请享用!

我使用这个:

 public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> instance, int partitionSize) { return instance .Select((value, index) => new { Index = index, Value = value }) .GroupBy(i => i.Index / partitionSize) .Select(i => i.Select(i2 => i2.Value)); } 

有趣的线程。 要获得分stream/分区的stream式版本,可以使用扩展方法从枚举器使用枚举器和产生序列。 使用yield将命令式代码转换为function式代码确实是一种非常强大的技术。

首先是一个枚举扩展,将元素的计数转换为一个惰性序列:

 public static IEnumerable<T> TakeFromCurrent<T>(this IEnumerator<T> enumerator, int count) { while (count > 0) { yield return enumerator.Current; if (--count > 0 && !enumerator.MoveNext()) yield break; } } 

然后是一个可分割的序列的可扩展的扩展:

 public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> seq, int partitionSize) { var enumerator = seq.GetEnumerator(); while (enumerator.MoveNext()) { yield return enumerator.TakeFromCurrent(partitionSize); } } 

最终的结果是一个非常高效,stream和懒惰的实现,依赖于非常简单的代码。

请享用!

这是有效的内存,并尽可能延迟执行(每批),并在线性时间O(n)

  public static IEnumerable<IEnumerable<T>> InBatchesOf<T>(this IEnumerable<T> items, int batchSize) { List<T> batch = new List<T>(batchSize); foreach (var item in items) { batch.Add(item); if (batch.Count >= batchSize) { yield return batch; batch = new List<T>(); } } if (batch.Count != 0) { //can't be batch size or would've yielded above batch.TrimExcess(); yield return batch; } } 

这个问题(和堂兄弟)有很多很好的答案。 我自己也需要这样做,并且创build了一个解决scheme,这个解决scheme在源集合可以被当作列表的场景中被devise为高效和容错的。 它不使用任何惰性迭代,因此可能不适合可能施加内存压力的未知大小的集合。

 static public IList<T[]> GetChunks<T>(this IEnumerable<T> source, int batchsize) { IList<T[]> result = null; if (source != null && batchsize > 0) { var list = source as List<T> ?? source.ToList(); if (list.Count > 0) { result = new List<T[]>(); for (var index = 0; index < list.Count; index += batchsize) { var rangesize = Math.Min(batchsize, list.Count - index); result.Add(list.GetRange(index, rangesize).ToArray()); } } } return result ?? Enumerable.Empty<T[]>().ToList(); } static public void TestGetChunks() { var ids = Enumerable.Range(1, 163).Select(i => i.ToString()); foreach (var chunk in ids.GetChunks(20)) { Console.WriteLine("[{0}]", String.Join(",", chunk)); } } 

我在使用GetRange和Math.Min的这一系列问题中看到了一些答案。 但是我相信总的来说,这是一个在错误检查和效率方面更完整的解决scheme。

  protected List<List<int>> MySplit(int MaxNumber, int Divider) { List<List<int>> lst = new List<List<int>>(); int ListCount = 0; int d = MaxNumber / Divider; lst.Add(new List<int>()); for (int i = 1; i <= MaxNumber; i++) { lst[ListCount].Add(i); if (i != 0 && i % d == 0) { ListCount++; d += MaxNumber / Divider; lst.Add(new List<int>()); } } return lst; } 

伟大的答案,我的情况下,我testing了接受的答案,它似乎不保持秩序。 Nawfal也有很好的回答。 但在我的情况下,我想以正常化的方式分割剩下的部分,我看到的所有答案都是在剩下的部分或开头或结尾处传播。

我的答案也使剩下的更加正常化的方式传播。

  static class Program { static void Main(string[] args) { var input = new List<String>(); for (int k = 0; k < 18; ++k) { input.Add(k.ToString()); } var result = splitListIntoSmallerLists(input, 15); int i = 0; foreach(var resul in result){ Console.WriteLine("------Segment:" + i.ToString() + "--------"); foreach(var res in resul){ Console.WriteLine(res); } i++; } Console.ReadLine(); } private static List<List<T>> splitListIntoSmallerLists<T>(List<T> i_bigList,int i_numberOfSmallerLists) { if (i_numberOfSmallerLists <= 0) throw new ArgumentOutOfRangeException("Illegal value of numberOfSmallLists"); int normalizedSpreadRemainderCounter = 0; int normalizedSpreadNumber = 0; //eg 7 /5 > 0 ==> output size is 5 , 2 /5 < 0 ==> output is 2 int minimumNumberOfPartsInEachSmallerList = i_bigList.Count / i_numberOfSmallerLists; int remainder = i_bigList.Count % i_numberOfSmallerLists; int outputSize = minimumNumberOfPartsInEachSmallerList > 0 ? i_numberOfSmallerLists : remainder; //In case remainder > 0 we want to spread the remainder equally between the others if (remainder > 0) { if (minimumNumberOfPartsInEachSmallerList > 0) { normalizedSpreadNumber = (int)Math.Floor((double)i_numberOfSmallerLists / remainder); } else { normalizedSpreadNumber = 1; } } List<List<T>> retVal = new List<List<T>>(outputSize); int inputIndex = 0; for (int i = 0; i < outputSize; ++i) { retVal.Add(new List<T>()); if (minimumNumberOfPartsInEachSmallerList > 0) { retVal[i].AddRange(i_bigList.GetRange(inputIndex, minimumNumberOfPartsInEachSmallerList)); inputIndex += minimumNumberOfPartsInEachSmallerList; } //If we have remainder take one from it, if our counter is equal to normalizedSpreadNumber. if (remainder > 0) { if (normalizedSpreadRemainderCounter == normalizedSpreadNumber-1) { retVal[i].Add(i_bigList[inputIndex]); remainder--; inputIndex++; normalizedSpreadRemainderCounter=0; } else { normalizedSpreadRemainderCounter++; } } } return retVal; } } 

如果在这些部分的顺序不是很重要,你可以试试这个:

 int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int n = 3; var result = array.Select((value, index) => new { Value = value, Index = index }).GroupBy(i => i.Index % n, i => i.Value); // or var result2 = from i in array.Select((value, index) => new { Value = value, Index = index }) group i.Value by i.Index % n into g select g; 

但是,这些不能被转换为IEnumerable <IEnumerable <INT >>由于某种原因…

这是我的代码,很好,很短。

  <Extension()> Public Function Chunk(Of T)(ByVal this As IList(Of T), ByVal size As Integer) As List(Of List(Of T)) Dim result As New List(Of List(Of T)) For i = 0 To CInt(Math.Ceiling(this.Count / size)) - 1 result.Add(New List(Of T)(this.GetRange(i * size, Math.Min(size, this.Count - (i * size))))) Next Return result End Function 

这是我的方式,列出项目和按行分列

  int repat_count=4; arrItems.ForEach((x, i) => { if (i % repat_count == 0) row = tbo.NewElement(el_tr, cls_min_height); var td = row.NewElement(el_td); td.innerHTML = x.Name; }); 

我正在寻找像string一样的分裂,所以整个列表根据一些规则分裂,不仅是第一部分,这是我的解决scheme

 List<int> sequence = new List<int>(); for (int i = 0; i < 2000; i++) { sequence.Add(i); } int splitIndex = 900; List<List<int>> splitted = new List<List<int>>(); while (sequence.Count != 0) { splitted.Add(sequence.Take(splitIndex).ToList() ); sequence.RemoveRange(0, Math.Min(splitIndex, sequence.Count)); } 

这里是一些项目的数量而不是部分数量的调整:

 public static class MiscExctensions { public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int nbItems) { return ( list .Select((o, n) => new { o, n }) .GroupBy(g => (int)(gn / nbItems)) .Select(g => g.Select(x => xo)) ); } } 
 int[] items = new int[] { 0,1,2,3,4,5,6,7,8,9, 10 }; int itemIndex = 0; int groupSize = 2; int nextGroup = groupSize; var seqItems = from aItem in items group aItem by (itemIndex++ < nextGroup) ? nextGroup / groupSize : (nextGroup += groupSize) / groupSize into itemGroup select itemGroup.AsEnumerable(); 

刚刚遇到这个线程,这里的大部分解决scheme都涉及到向集合中添加项目,在返回之前有效地实现每个页面。 这是不好的,原因有二:首先,如果页面很大,填充页面会产生内存开销;其次,当前进到下一个logging时,会有迭代器使迭代器无效(例如,如果在枚举器方法中包装DataReader) 。

该解决scheme使用两个嵌套的枚举器方法来避免将项目caching到临时集合中。 由于外层迭代器和内层迭代器遍历相同的枚举types,因此它们必然共享相同的枚举器,因此,在处理完当前页面之前,不要推进外层迭代器。 也就是说,如果您决定不遍历当前页面,那么当您移动到下一页时,此解决scheme将自动迭代到页面边界。

 using System.Collections.Generic; public static class EnumerableExtensions { /// <summary> /// Partitions an enumerable into individual pages of a specified size, still scanning the source enumerable just once /// </summary> /// <typeparam name="T">The element type</typeparam> /// <param name="enumerable">The source enumerable</param> /// <param name="pageSize">The number of elements to return in each page</param> /// <returns></returns> public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> enumerable, int pageSize) { var enumerator = enumerable.GetEnumerator(); while (enumerator.MoveNext()) { var indexWithinPage = new IntByRef { Value = 0 }; yield return SubPartition(enumerator, pageSize, indexWithinPage); // Continue iterating through any remaining items in the page, to align with the start of the next page for (; indexWithinPage.Value < pageSize; indexWithinPage.Value++) { if (!enumerator.MoveNext()) { yield break; } } } } private static IEnumerable<T> SubPartition<T>(IEnumerator<T> enumerator, int pageSize, IntByRef index) { for (; index.Value < pageSize; index.Value++) { yield return enumerator.Current; if (!enumerator.MoveNext()) { yield break; } } } private class IntByRef { public int Value { get; set; } } }