是否有任何方法可以将List<SomeObject>分离为SomeObject的几个单独的列表,使用项目索引作为每个分割的分隔符?
让我举个例子:
我有一个List<SomeObject>,我需要一个List<List<SomeObject>>或List<SomeObject>[],这样每个结果列表将包含一组原始列表的3个项目(依次)。
eg.:
原始列表:[a, g, e, w, p, s, q, f, x, y, i, m, c]
结果列表:[a、g e], [w、p, s], [q, f, x]、[y,我,m], [c]
我还需要结果列表的大小是这个函数的参数。
好吧,以下是我的看法:
完全懒惰:工作在无限枚举上
没有中间复制/缓冲
O(n)执行时间
当内部序列仅被部分消耗时也适用
public static IEnumerable<IEnumerable<T>> Chunks<T>(this IEnumerable<T> enumerable,
int chunkSize)
{
if (chunkSize < 1) throw new ArgumentException("chunkSize must be positive");
using (var e = enumerable.GetEnumerator())
while (e.MoveNext())
{
var remaining = chunkSize; // elements remaining in the current chunk
var innerMoveNext = new Func<bool>(() => --remaining > 0 && e.MoveNext());
yield return e.GetChunk(innerMoveNext);
while (innerMoveNext()) {/* discard elements skipped by inner iterator */}
}
}
private static IEnumerable<T> GetChunk<T>(this IEnumerator<T> e,
Func<bool> innerMoveNext)
{
do yield return e.Current;
while (innerMoveNext());
}
Example Usage
var src = new [] {1, 2, 3, 4, 5, 6};
var c3 = src.Chunks(3); // {{1, 2, 3}, {4, 5, 6}};
var c4 = src.Chunks(4); // {{1, 2, 3, 4}, {5, 6}};
var sum = c3.Select(c => c.Sum()); // {6, 15}
var count = c3.Count(); // 2
var take2 = c3.Select(c => c.Take(2)); // {{1, 2}, {4, 5}}
Explanations
The code works by nesting two yield based iterators.
The outer iterator must keep track of how many elements have been effectively consumed by the inner (chunk) iterator. This is done by closing over remaining with innerMoveNext(). Unconsumed elements of a chunk are discarded before the next chunk is yielded by the outer iterator.
This is necessary because otherwise you get inconsistent results, when the inner enumerables are not (completely) consumed (e.g. c3.Count() would return 6).
Note: The answer has been updated to address the shortcomings pointed out by @aolszowka.
没有办法在一个解决方案中结合所有理想的特性,如完全懒惰、无复制、完全通用性和安全性。最根本的原因是不能保证在访问块之前输入不发生变化。
假设我们有一个如下签名的函数:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunkSize)
{
// Some implementation
}
那么下面的使用方式就有问题了:
var myList = new List<int>()
{
1,2,3,4
};
var myChunks = myList.Chunk(2);
myList.RemoveAt(0);
var firstChunk = myChunks.First();
Console.WriteLine("First chunk:" + String.Join(',', firstChunk));
myList.RemoveAt(0);
var secondChunk = myChunks.Skip(1).First();
Console.WriteLine("Second chunk:" + String.Join(',', secondChunk));
// What outputs do we see for first and second chunk? Probably not what you would expect...
根据具体的实现,代码将失败并产生运行时错误或产生不直观的结果。
所以,至少有一个属性需要减弱。如果你想要一个无懈无击的惰性解决方案,你需要将输入类型限制为不可变类型,即使这样也不能直接覆盖所有用例。但是,如果您可以控制使用,您仍然可以选择最通用的解决方案,只要您确保以一种有效的方式使用它。否则,你可能会放弃懒惰,接受一定数量的复制。
最后,这完全取决于您的用例和需求,哪种解决方案最适合您。