在linq中创build批次
有人可以build议一种方法来创buildlinq中的一定大小的批次?
理想情况下,我希望能够以一些可configuration数量的块进行操作。
你不需要写任何代码。 使用MoreLINQ Batch方法,将源序列批量处理成大小的桶(更多的LINQ可作为NuGet包提供,您可以安装):
int size = 10; var batches = sequence.Batch(size);
其中实现为:
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>( this IEnumerable<TSource> source, int size) { TSource[] bucket = null; var count = 0; foreach (var item in source) { if (bucket == null) bucket = new TSource[size]; bucket[count++] = item; if (count != size) continue; yield return bucket; bucket = null; count = 0; } if (bucket != null && count > 0) yield return bucket.Take(count); }
public static class MyExtensions { public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems) { return items.Select((item, inx) => new { item, inx }) .GroupBy(x => x.inx / maxItems) .Select(g => g.Select(x => x.item)); } }
用法是:
List<int> list = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; foreach(var batch in list.Batch(3)) { Console.WriteLine(String.Join(",",batch)); }
OUTPUT:
0,1,2 3,4,5 6,7,8 9
以上所有内容都是大批量或低内存空间。 不得不写我自己的pipe道(通知任何地方没有任何物品):
public static class BatchLinq { public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size) { if (size <= 0) throw new ArgumentOutOfRangeException("size", "Must be greater than zero."); using (IEnumerator<T> enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) yield return TakeIEnumerator(enumerator, size); } private static IEnumerable<T> TakeIEnumerator<T>(IEnumerator<T> source, int size) { int i = 0; do yield return source.Current; while (++i < size && source.MoveNext()); } }
编辑:这种方法的已知问题是每个批次必须枚举并完全枚举,然后再转移到下一批次。 例如,这不起作用:
//Select first item of every 100 items Batch(list, 100).Select(b => b.First())
如果从定义为IEnumerable<T>
sequence
开始,并且您知道可以安全地枚举多次(例如,因为它是一个数组或列表),则可以使用此简单模式批量处理元素:
while (sequence.Any()) { var batch = sequence.Take(10); sequence = sequence.Skip(10); // do whatever you need to do with each batch here }
我join这个很晚,但我发现更有趣的东西。
所以我们可以在这里使用Skip
和Take
获得更好的性能。
public static class MyExtensions { public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int maxItems) { return items.Select((item, index) => new { item, index }) .GroupBy(x => x.index / maxItems) .Select(g => g.Select(x => x.item)); } public static IEnumerable<T> Batch2<T>(this IEnumerable<T> items, int skip, int take) { return items.Skip(skip).Take(take); } }
接下来我检查了100000条logging。 循环只是在Batch
情况下花费更多的时间
代码控制台应用程序。
static void Main(string[] args) { List<string> Ids = GetData("First"); List<string> Ids2 = GetData("tsriF"); Stopwatch FirstWatch = new Stopwatch(); FirstWatch.Start(); foreach (var batch in Ids2.Batch(5000)) { // Console.WriteLine("Batch Ouput:= " + string.Join(",", batch)); } FirstWatch.Stop(); Console.WriteLine("Done Processing time taken:= "+ FirstWatch.Elapsed.ToString()); Stopwatch Second = new Stopwatch(); Second.Start(); int Length = Ids2.Count; int StartIndex = 0; int BatchSize = 5000; while (Length > 0) { var SecBatch = Ids2.Batch2(StartIndex, BatchSize); // Console.WriteLine("Second Batch Ouput:= " + string.Join(",", SecBatch)); Length = Length - BatchSize; StartIndex += BatchSize; } Second.Stop(); Console.WriteLine("Done Processing time taken Second:= " + Second.Elapsed.ToString()); Console.ReadKey(); } static List<string> GetData(string name) { List<string> Data = new List<string>(); for (int i = 0; i < 100000; i++) { Data.Add(string.Format("{0} {1}", name, i.ToString())); } return Data; }
所花的时间是这样的。
首先 – 00:00:00.0708,00:00:00.0660
第二个(跳过和跳过) – 00:00:00.0008,00:00:00.0008
与MoreLINQ相同,但使用List而不是Array。 我没有做基准testing,但可读性对于一些人来说更重要:
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size) { List<T> batch = new List<T>(); foreach (var item in source) { batch.Add(item); if (batch.Count >= size) { yield return batch; batch.Clear(); } } if (batch.Count > 0) { yield return batch; } }
这是Nick Whaley在上面的post的扩展,它解决了第一个问题。
元素仍然必须按照严格的顺序枚举不超过一次。 如果某些元素在内部循环中没有被使用,它们将被丢弃(并且试图通过保存的迭代器再次访问它们将抛出InvalidOperationException: Enumeration already finished.
)。
您可以在.NET Fiddle中testing完整的示例。
public static class BatchLinq { public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size) { if (size <= 0) throw new ArgumentOutOfRangeException("size", "Must be greater than zero."); using (var enumerator = source.GetEnumerator()) while (enumerator.MoveNext()) { var i = new BatchInner(); var e = i.BatchInner(enumerator, size); yield return e; if (!i.done) e.Count(); } } private class BatchInner { public bool done = false; public IEnumerable<T> BatchInner<T>(IEnumerator<T> source, int size) { int i = 0; do yield return source.Current; while (++i < size && source.MoveNext()); done = true; } } }
我想不出有什么方法可以在没有包装的情况下获得done
价值。 因此,不幸的是每个批次都会产生一次分配(如果没有优化,我不知道是否可以)。 我想把它从外部循环中提出来,但这可能会导致多个迭代器同时工作的问题。 它可能会起作用,但是我必须比我更确定周围的确切语义。
static IEnumerable<IEnumerable<T>> TakeBatch<T>(IEnumerable<T> ts,int batchSize) { return from @group in ts.Select((x, i) => new { x, i }).ToLookup(xi => xi.i / batchSize) select @group.Select(xi => xi.x); }