是否有任何方法可以将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]
我还需要结果列表的大小是这个函数的参数。
更新。net 6.0
. net 6.0为系统添加了一个新的原生Chunk方法。Linq命名空间:
public static System.Collections.Generic.IEnumerable<TSource[]> Chunk<TSource> (
this System.Collections.Generic.IEnumerable<TSource> source, int size);
使用这种新方法,除了最后一个块外,每个块都是大小相同的。最后一个块将包含剩余的元素,大小可能较小。
这里有一个例子:
var list = Enumerable.Range(1, 100);
var chunkSize = 10;
foreach(var chunk in list.Chunk(chunkSize)) //Returns a chunk with the correct size.
{
Parallel.ForEach(chunk, (item) =>
{
//Do something Parallel here.
Console.WriteLine(item);
});
}
你可能会想,为什么不使用Skip and Take呢?这是真的,我认为这更简洁,让事情更有可读性。
如果源集合实现了IList < T >(按索引随机访问),我们可以使用下面的方法。它只在真正访问元素时获取元素,因此这对于延迟求值的集合特别有用。类似于unbounded IEnumerable< T >,但是对于IList< T >。
public static IEnumerable<IEnumerable<T>> Chunkify<T>(this IList<T> src, int chunkSize)
{
if (src == null) throw new ArgumentNullException(nameof(src));
if (chunkSize < 1) throw new ArgumentOutOfRangeException(nameof(chunkSize), $"must be > 0, got {chunkSize}");
for(var ci = 0; ci <= src.Count/chunkSize; ci++){
yield return Window(src, ci*chunkSize, Math.Min((ci+1)*chunkSize, src.Count)-1);
}
}
private static IEnumerable<T> Window<T>(IList<T> src, int startIdx, int endIdx)
{
Console.WriteLine($"window {startIdx} - {endIdx}");
while(startIdx <= endIdx){
yield return src[startIdx++];
}
}
好吧,以下是我的看法:
完全懒惰:工作在无限枚举上
没有中间复制/缓冲
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.
可与无限发电机工作:
a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1)))
.Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1)))
.Where((x, i) => i % 3 == 0)
演示代码:https://ideone.com/GKmL7M
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
private static void DoIt(IEnumerable<int> a)
{
Console.WriteLine(String.Join(" ", a));
foreach (var x in a.Zip(a.Skip(1), (x, y) => Enumerable.Repeat(x, 1).Concat(Enumerable.Repeat(y, 1))).Zip(a.Skip(2), (xy, z) => xy.Concat(Enumerable.Repeat(z, 1))).Where((x, i) => i % 3 == 0))
Console.WriteLine(String.Join(" ", x));
Console.WriteLine();
}
public static void Main()
{
DoIt(new int[] {1});
DoIt(new int[] {1, 2});
DoIt(new int[] {1, 2, 3});
DoIt(new int[] {1, 2, 3, 4});
DoIt(new int[] {1, 2, 3, 4, 5});
DoIt(new int[] {1, 2, 3, 4, 5, 6});
}
}
1
1 2
1 2 3
1 2 3
1 2 3 4
1 2 3
1 2 3 4 5
1 2 3
1 2 3 4 5 6
1 2 3
4 5 6
但实际上我更喜欢写相应的方法没有linq。