是否有任何方法可以将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]
我还需要结果列表的大小是这个函数的参数。
看看这个!我有一个序列计数器和日期的元素列表。每次序列重新启动时,我都想创建一个新列表。
例:消息列表。
List<dynamic> messages = new List<dynamic>
{
new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
};
我想在计数器重新启动时将列表拆分为单独的列表。代码如下:
var arraylist = new List<List<dynamic>>();
List<dynamic> messages = new List<dynamic>
{
new { FcntUp = 101, CommTimestamp = "2019-01-01 00:00:01" },
new { FcntUp = 102, CommTimestamp = "2019-01-01 00:00:02" },
new { FcntUp = 103, CommTimestamp = "2019-01-01 00:00:03" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:04" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:05" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:06" },
//restart of sequence
new { FcntUp = 1, CommTimestamp = "2019-01-01 00:00:07" },
new { FcntUp = 2, CommTimestamp = "2019-01-01 00:00:08" },
new { FcntUp = 3, CommTimestamp = "2019-01-01 00:00:09" }
};
//group by FcntUp and CommTimestamp
var query = messages.GroupBy(x => new { x.FcntUp, x.CommTimestamp });
//declare the current item
dynamic currentItem = null;
//declare the list of ranges
List<dynamic> range = null;
//loop through the sorted list
foreach (var item in query)
{
//check if start of new range
if (currentItem == null || item.Key.FcntUp < currentItem.Key.FcntUp)
{
//create a new list if the FcntUp starts on a new range
range = new List<dynamic>();
//add the list to the parent list
arraylist.Add(range);
}
//add the item to the sublist
range.Add(item);
//set the current item
currentItem = item;
}
更新。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呢?这是真的,我认为这更简洁,让事情更有可读性。
好吧,以下是我的看法:
完全懒惰:工作在无限枚举上
没有中间复制/缓冲
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.