有没有一种很好的方法可以n
使用LINQ 将集合拆分为多个部分?当然不一定均匀.
也就是说,我想将集合划分为子集合,每个子集合包含元素的子集,其中最后一个集合可以是不规则的.
纯linq和最简单的解决方案如下所示.
static class LinqExtensions { public static IEnumerable> Split (this IEnumerable 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 to Objects的一组添加中编写一个.Marc Gravell 在这里有一个实现,虽然我可能会修改它以返回一个只读视图:
public static IEnumerable> Partition (this IEnumerable 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 (array); array = null; count = 0; } } if (array != null) { Array.Resize(ref array, count); yield return new ReadOnlyCollection (array); } }
static class LinqExtensions { public static IEnumerable> Split (this IEnumerable list, int parts) { return list.Select((item, index) => new {index, item}) .GroupBy(x => x.index % parts) .Select(x => x.Select(y => y.item)); } }
好吧,我会戴上帽子.我的算法的优点:
没有昂贵的乘法,除法或模数运算符
所有操作都是O(1)(见下面的注释)
适用于IEnumerable <> source(不需要Count属性)
简单
代码:
public static IEnumerable> Section (this IEnumerable source, int length) { if (length <= 0) throw new ArgumentOutOfRangeException("length"); var section = new List (length); foreach (var item in source) { section.Add(item); if (section.Count == length) { yield return section.AsReadOnly(); section = new List (length); } } if (section.Count > 0) yield return section.AsReadOnly(); }
正如下面的评论中指出的那样,这种方法实际上并没有解决原始问题,该问题要求固定数量的长度近似相等的部分.也就是说,您仍然可以通过这种方式调用原始问题来解决原始问题:
myEnum.Section(myEnum.Count() / number_of_sections + 1)
当以这种方式使用时,该方法不再是O(1),因为Count()操作是O(N).
这与接受的答案相同,但更简单的表示:
public static IEnumerable> Split (this IEnumerable items, int numOfParts) { int i = 0; return items.GroupBy(x => i++ % numOfParts); }
上述方法将一个IEnumerable
相等大小或接近相等大小的块分成N个.
public static IEnumerable> Partition (this IEnumerable items, int partitionSize) { int i = 0; return items.GroupBy(x => i++ / partitionSize).ToArray(); }
上面的方法将一个IEnumerable
块分成所需的固定大小的块,块的总数是不重要的 - 这不是问题所在.
Split
除了速度较慢之外,该方法的问题在于,在分组将基于每个位置的N的倍数进行分组的意义上,它会对输出进行加扰,或者换句话说,您没有得到块.按原始顺序.
这里几乎每个答案要么不保留顺序,要么是分区而不是分裂,或者显然是错误的.试试这个更快,保留订单但是更详细:
public static IEnumerable> Split (this ICollection 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
操作的等效方法
我一直在使用我之前发布的分区功能.关于它的唯一坏处是它不是完全流式传输.如果您使用序列中的少数元素,这不是问题.当我开始使用我的序列中的100.000+个元素时,我需要一个新的解决方案.
以下解决方案要复杂得多(以及更多代码!),但它非常有效.
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> Partition (this IEnumerable 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 (enumerable, partitionSize); } } class PartitioningEnumerable : IEnumerable > { IEnumerable _enumerable; int _partitionSize; public PartitioningEnumerable(IEnumerable enumerable, int partitionSize) { _enumerable = enumerable; _partitionSize = partitionSize; } public IEnumerator > GetEnumerator() { return new PartitioningEnumerator (_enumerable.GetEnumerator(), _partitionSize); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } class PartitioningEnumerator : IEnumerator > { IEnumerator _enumerator; int _partitionSize; public PartitioningEnumerator(IEnumerator enumerator, int partitionSize) { _enumerator = enumerator; _partitionSize = partitionSize; } public void Dispose() { _enumerator.Dispose(); } IEnumerable _current; public IEnumerable 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 (_enumerator, _partitionSize); result = true; } else { _current = null; result = false; } return result; } } class PartitionEnumerable : IEnumerable { IEnumerator _enumerator; int _partitionSize; public PartitionEnumerable(IEnumerator enumerator, int partitionSize) { _enumerator = enumerator; _partitionSize = partitionSize; } public IEnumerator GetEnumerator() { return new PartitionEnumerator (_enumerator, _partitionSize); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } class PartitionEnumerator : IEnumerator { IEnumerator _enumerator; int _partitionSize; int _count; public PartitionEnumerator(IEnumerator 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; } } }
请享用!