Câu trả lời:
Bạn không cần phải viết bất kỳ mã nào. Sử dụng phương pháp MoreLINQ Batch, chia chuỗi nguồn thành các nhóm có kích thước (MoreLINQ có sẵn dưới dạng gói NuGet mà bạn có thể cài đặt):
int size = 10;
var batches = sequence.Batch(size);
Được thực hiện như:
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).ToArray();
}
Batch(new int[] { 1, 2 }, 1000000)
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));
}
}
và cách sử dụng sẽ là:
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));
}
ĐẦU RA:
0,1,2
3,4,5
6,7,8
9
GroupBy
bắt đầu liệt kê, nó không phải liệt kê đầy đủ nguồn của nó sao? Điều này làm mất đánh giá lười biếng của nguồn và do đó, trong một số trường hợp, tất cả lợi ích của việc phân phối!
Nếu bạn bắt đầu với sequence
định nghĩa là một IEnumerable<T>
và bạn biết rằng nó có thể được liệt kê nhiều lần một cách an toàn (ví dụ: vì nó là một mảng hoặc một danh sách), bạn chỉ có thể sử dụng mẫu đơn giản này để xử lý các phần tử theo lô:
while (sequence.Any())
{
var batch = sequence.Take(10);
sequence = sequence.Skip(10);
// do whatever you need to do with each batch here
}
Tất cả những điều trên hoạt động rất tệ với các lô lớn hoặc không gian bộ nhớ thấp. Đã phải viết đường ống dẫn sẽ của riêng tôi (chú ý rằng không có mục tích lũy ở bất kỳ đâu):
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());
}
}
Chỉnh sửa: Vấn đề đã biết với cách tiếp cận này là mỗi lô phải được liệt kê và liệt kê đầy đủ trước khi chuyển sang lô tiếp theo. Ví dụ, điều này không hoạt động:
//Select first item of every 100 items
Batch(list, 100).Select(b => b.First())
Đây là một triển khai Batch hoàn toàn lười biếng, chi phí thấp, một chức năng mà không thực hiện bất kỳ tích lũy nào. Dựa trên (và khắc phục các sự cố trong) giải pháp của Nick Whaley với sự trợ giúp từ EricRoller.
Sự lặp lại đến trực tiếp từ IEnumerable bên dưới, vì vậy các phần tử phải được liệt kê theo thứ tự nghiêm ngặt và được truy cập không quá một lần. Nếu một số phần tử không được sử dụng trong một vòng lặp bên trong, chúng sẽ bị loại bỏ (và việc cố gắng truy cập lại chúng thông qua một trình vòng lặp đã lưu sẽ bị ném InvalidOperationException: Enumeration already finished.
).
Bạn có thể kiểm tra một mẫu hoàn chỉnh tại .NET Fiddle .
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())
{
int i = 0;
// Batch is a local function closing over `i` and `enumerator` that
// executes the inner batch enumeration
IEnumerable<T> Batch()
{
do yield return enumerator.Current;
while (++i < size && enumerator.MoveNext());
}
yield return Batch();
while (++i < size && enumerator.MoveNext()); // discard skipped items
}
}
}
done
bằng cách luôn gọi e.Count()
sau yield return e
. Bạn sẽ cần sắp xếp lại vòng lặp trong BatchInner để không gọi hành vi không xác định source.Current
nếu i >= size
. Điều này sẽ loại bỏ sự cần thiết phải phân bổ mới BatchInner
cho mỗi lô.
i
vì vậy điều này không nhất thiết phải hiệu quả hơn việc xác định một lớp riêng biệt, nhưng tôi nghĩ nó gọn gàng hơn một chút.
Tôi tự hỏi tại sao không ai đã từng đăng một giải pháp vòng lặp cho trường học cũ. Đây là một:
List<int> source = Enumerable.Range(1,23).ToList();
int batchsize = 10;
for (int i = 0; i < source.Count; i+= batchsize)
{
var batch = source.Skip(i).Take(batchsize);
}
Sự đơn giản này có thể thực hiện được vì phương pháp Take:
... liệt kê
source
và tạo ra các phần tử cho đến khicount
các phần tử đã được sinh ra hoặcsource
không chứa phần tử nào nữa. Nếucount
vượt quá số phần tử trongsource
, tất cả các phần tử củasource
được trả về
Tuyên bố từ chối trách nhiệm:
Sử dụng Skip và Take bên trong vòng lặp có nghĩa là enumerable sẽ được liệt kê nhiều lần. Điều này rất nguy hiểm nếu việc liệt kê bị trì hoãn. Nó có thể dẫn đến nhiều lần thực thi một truy vấn cơ sở dữ liệu hoặc một yêu cầu web hoặc một tệp đọc. Ví dụ này rõ ràng cho việc sử dụng Danh sách không bị trì hoãn, vì vậy nó ít có vấn đề hơn. Nó vẫn là một giải pháp chậm vì bỏ qua sẽ liệt kê bộ sưu tập mỗi khi nó được gọi.
Điều này cũng có thể được giải quyết bằng cách sử dụng GetRange
phương pháp này, nhưng nó yêu cầu một phép tính bổ sung để trích xuất một lô nghỉ có thể:
for (int i = 0; i < source.Count; i += batchsize)
{
int remaining = source.Count - i;
var batch = remaining > batchsize ? source.GetRange(i, batchsize) : source.GetRange(i, remaining);
}
Đây là cách thứ ba để xử lý điều này, hoạt động với 2 vòng lặp. Điều này đảm bảo rằng bộ sưu tập chỉ được liệt kê 1 lần !:
int batchsize = 10;
List<int> batch = new List<int>(batchsize);
for (int i = 0; i < source.Count; i += batchsize)
{
// calculated the remaining items to avoid an OutOfRangeException
batchsize = source.Count - i > batchsize ? batchsize : source.Count - i;
for (int j = i; j < i + batchsize; j++)
{
batch.Add(source[j]);
}
batch.Clear();
}
Skip
và Take
bên trong vòng lặp có nghĩa là có thể liệt kê nhiều lần. Điều này rất nguy hiểm nếu việc liệt kê bị trì hoãn. Nó có thể dẫn đến nhiều lần thực thi một truy vấn cơ sở dữ liệu hoặc một yêu cầu web hoặc một tệp đọc. Trong ví dụ của bạn, bạn có một List
không được hoãn lại, vì vậy nó ít có vấn đề hơn.
Cách tiếp cận tương tự như MoreLINQ, nhưng sử dụng Danh sách thay vì Mảng. Tôi chưa thực hiện đo điểm chuẩn, nhưng khả năng đọc quan trọng hơn đối với một số người:
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;
}
}
size
tham số vào của bạn new List
để tối ưu hóa kích thước của nó.
batch.Clear();
vớibatch = new List<T>();
Đây là một nỗ lực cải tiến triển khai lười biếng của Nick Whaley ( liên kết ) và infogulch ( liên kết ) Batch
. Điều này là nghiêm ngặt. Bạn có thể liệt kê các lô theo đúng thứ tự hoặc bạn có một ngoại lệ.
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
this IEnumerable<TSource> source, int size)
{
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
using (var enumerator = source.GetEnumerator())
{
int i = 0;
while (enumerator.MoveNext())
{
if (i % size != 0) throw new InvalidOperationException(
"The enumeration is out of order.");
i++;
yield return GetBatch();
}
IEnumerable<TSource> GetBatch()
{
while (true)
{
yield return enumerator.Current;
if (i % size == 0 || !enumerator.MoveNext()) break;
i++;
}
}
}
}
Và đây là một Batch
triển khai lười biếng cho các loại nguồn IList<T>
. Điều này không áp đặt hạn chế đối với việc liệt kê. Các lô có thể được liệt kê một phần, theo bất kỳ thứ tự nào và nhiều hơn một lần. Mặc dù vậy, hạn chế của việc không sửa đổi bộ sưu tập trong quá trình điều tra. Điều này đạt được bằng cách thực hiện một cuộc gọi giả tới enumerator.MoveNext()
trước khi mang lại bất kỳ đoạn hoặc phần tử nào. Nhược điểm là không thể tranh cãi được điều tra viên, vì không biết khi nào kết thúc điều tra.
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
this IList<TSource> source, int size)
{
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
var enumerator = source.GetEnumerator();
for (int i = 0; i < source.Count; i += size)
{
enumerator.MoveNext();
yield return GetChunk(i, Math.Min(i + size, source.Count));
}
IEnumerable<TSource> GetChunk(int from, int toExclusive)
{
for (int j = from; j < toExclusive; j++)
{
enumerator.MoveNext();
yield return source[j];
}
}
}
Tôi tham gia chương trình này rất muộn nhưng tôi đã tìm thấy điều gì đó thú vị hơn.
Vì vậy, chúng tôi có thể sử dụng ở đây Skip
và Take
cho hiệu suất tốt hơn.
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);
}
}
Tiếp theo tôi đã kiểm tra với 100000 bản ghi. Việc lặp lại chỉ mất nhiều thời gian hơn trong trường hợpBatch
Code Of ứng dụng console.
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;
}
Thời gian thực hiện Là như thế này.
Đầu tiên - 00: 00: 00.0708, 00: 00: 00.0660
Thứ hai (Lấy và Bỏ qua Một) - 00: 00: 00.0008, 00: 00: 00.0008
GroupBy
liệt kê đầy đủ trước khi nó tạo ra một hàng. Đây không phải là một cách tốt để làm theo lô.
foreach (var batch in Ids2.Batch(5000))
thành var gourpBatch = Ids2.Batch(5000)
và kiểm tra kết quả hẹn giờ. hoặc thêm vào danh sách var SecBatch = Ids2.Batch2(StartIndex, BatchSize);
tôi sẽ quan tâm nếu kết quả của bạn cho thời gian thay đổi.
Vì vậy, với một chiếc mũ chức năng được bật, điều này có vẻ tầm thường .... nhưng trong C #, có một số nhược điểm đáng kể.
bạn có thể xem đây là tài liệu mở rộng của IEnumerable (google nó và bạn có thể kết thúc trong một số tài liệu Haskell, nhưng có thể có một số nội dung F # sử dụng mở rộng, nếu bạn biết F #, hãy liếc nhìn tài liệu Haskell và nó sẽ làm giác quan).
Unfold có liên quan đến gấp ("tổng hợp") ngoại trừ việc lặp lại thông qua IEnumerable đầu vào, nó lặp lại qua các cấu trúc dữ liệu đầu ra (mối quan hệ tương tự giữa IEnumerable và IObservable, trên thực tế, tôi nghĩ IObservable thực hiện "mở" được gọi là tạo. ..)
Dù sao thì trước tiên, bạn cần một phương thức mở, tôi nghĩ điều này hoạt động (tiếc là cuối cùng nó sẽ làm nổ tung ngăn xếp đối với các "danh sách" lớn ... bạn có thể viết điều này một cách an toàn trong F # bằng cách sử dụng sản lượng thay vì concat);
static IEnumerable<T> Unfold<T, U>(Func<U, IEnumerable<Tuple<U, T>>> f, U seed)
{
var maybeNewSeedAndElement = f(seed);
return maybeNewSeedAndElement.SelectMany(x => new[] { x.Item2 }.Concat(Unfold(f, x.Item1)));
}
điều này hơi khó hiểu vì C # không triển khai một số thứ mà các ngôn ngữ chức năng coi là đương nhiên ... nhưng về cơ bản nó sẽ lấy một hạt giống và sau đó tạo ra câu trả lời "Có thể" của phần tử tiếp theo trong IEnumerable và hạt giống tiếp theo (Có thể không tồn tại trong C #, vì vậy chúng tôi đã sử dụng IEnumerable để giả mạo nó), và nối phần còn lại của câu trả lời (Tôi không thể đảm bảo cho độ phức tạp "O (n?)" của điều này).
Khi bạn đã làm xong điều đó rồi;
static IEnumerable<IEnumerable<T>> Batch<T>(IEnumerable<T> xs, int n)
{
return Unfold(ys =>
{
var head = ys.Take(n);
var tail = ys.Skip(n);
return head.Take(1).Select(_ => Tuple.Create(tail, head));
},
xs);
}
tất cả trông khá sạch sẽ ... bạn lấy phần tử "n" làm phần tử "tiếp theo" trong IEnumerable, và "đuôi" là phần còn lại của danh sách chưa được xử lý.
nếu không có gì trong đầu ... bạn đã kết thúc ... bạn trả về "Không có gì" (nhưng được giả mạo là IEnumerable trống>) ... nếu không bạn trả lại phần tử đầu và đuôi để xử lý.
bạn có thể làm điều này bằng cách sử dụng IObservable, có lẽ đã có một phương pháp giống như "Batch" ở đó và bạn có thể sử dụng phương pháp đó.
Nếu nguy cơ tràn ngăn xếp đáng lo ngại (có lẽ nên xảy ra), thì bạn nên triển khai trong F # (và có lẽ đã có một số thư viện F # (FSharpX?) Với điều này).
(Tôi chỉ mới thực hiện một số thử nghiệm thô sơ về điều này, vì vậy có thể có những lỗi kỳ lạ trong đó).
Tôi đã viết một triển khai IEnumerable tùy chỉnh hoạt động mà không cần linq và đảm bảo một cách liệt kê duy nhất trên dữ liệu. Nó cũng hoàn thành tất cả những điều này mà không yêu cầu danh sách hoặc mảng sao lưu gây ra vụ nổ bộ nhớ trên các tập dữ liệu lớn.
Dưới đây là một số bài kiểm tra cơ bản:
[Fact]
public void ShouldPartition()
{
var ints = new List<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
var data = ints.PartitionByMaxGroupSize(3);
data.Count().Should().Be(4);
data.Skip(0).First().Count().Should().Be(3);
data.Skip(0).First().ToList()[0].Should().Be(0);
data.Skip(0).First().ToList()[1].Should().Be(1);
data.Skip(0).First().ToList()[2].Should().Be(2);
data.Skip(1).First().Count().Should().Be(3);
data.Skip(1).First().ToList()[0].Should().Be(3);
data.Skip(1).First().ToList()[1].Should().Be(4);
data.Skip(1).First().ToList()[2].Should().Be(5);
data.Skip(2).First().Count().Should().Be(3);
data.Skip(2).First().ToList()[0].Should().Be(6);
data.Skip(2).First().ToList()[1].Should().Be(7);
data.Skip(2).First().ToList()[2].Should().Be(8);
data.Skip(3).First().Count().Should().Be(1);
data.Skip(3).First().ToList()[0].Should().Be(9);
}
Phương pháp mở rộng để phân vùng dữ liệu.
/// <summary>
/// A set of extension methods for <see cref="IEnumerable{T}"/>.
/// </summary>
public static class EnumerableExtender
{
/// <summary>
/// Splits an enumerable into chucks, by a maximum group size.
/// </summary>
/// <param name="source">The source to split</param>
/// <param name="maxSize">The maximum number of items per group.</param>
/// <typeparam name="T">The type of item to split</typeparam>
/// <returns>A list of lists of the original items.</returns>
public static IEnumerable<IEnumerable<T>> PartitionByMaxGroupSize<T>(this IEnumerable<T> source, int maxSize)
{
return new SplittingEnumerable<T>(source, maxSize);
}
}
Đây là lớp triển khai
using System.Collections;
using System.Collections.Generic;
internal class SplittingEnumerable<T> : IEnumerable<IEnumerable<T>>
{
private readonly IEnumerable<T> backing;
private readonly int maxSize;
private bool hasCurrent;
private T lastItem;
public SplittingEnumerable(IEnumerable<T> backing, int maxSize)
{
this.backing = backing;
this.maxSize = maxSize;
}
public IEnumerator<IEnumerable<T>> GetEnumerator()
{
return new Enumerator(this, this.backing.GetEnumerator());
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private class Enumerator : IEnumerator<IEnumerable<T>>
{
private readonly SplittingEnumerable<T> parent;
private readonly IEnumerator<T> backingEnumerator;
private NextEnumerable current;
public Enumerator(SplittingEnumerable<T> parent, IEnumerator<T> backingEnumerator)
{
this.parent = parent;
this.backingEnumerator = backingEnumerator;
this.parent.hasCurrent = this.backingEnumerator.MoveNext();
if (this.parent.hasCurrent)
{
this.parent.lastItem = this.backingEnumerator.Current;
}
}
public bool MoveNext()
{
if (this.current == null)
{
this.current = new NextEnumerable(this.parent, this.backingEnumerator);
return true;
}
else
{
if (!this.current.IsComplete)
{
using (var enumerator = this.current.GetEnumerator())
{
while (enumerator.MoveNext())
{
}
}
}
}
if (!this.parent.hasCurrent)
{
return false;
}
this.current = new NextEnumerable(this.parent, this.backingEnumerator);
return true;
}
public void Reset()
{
throw new System.NotImplementedException();
}
public IEnumerable<T> Current
{
get { return this.current; }
}
object IEnumerator.Current
{
get { return this.Current; }
}
public void Dispose()
{
}
}
private class NextEnumerable : IEnumerable<T>
{
private readonly SplittingEnumerable<T> splitter;
private readonly IEnumerator<T> backingEnumerator;
private int currentSize;
public NextEnumerable(SplittingEnumerable<T> splitter, IEnumerator<T> backingEnumerator)
{
this.splitter = splitter;
this.backingEnumerator = backingEnumerator;
}
public bool IsComplete { get; private set; }
public IEnumerator<T> GetEnumerator()
{
return new NextEnumerator(this.splitter, this, this.backingEnumerator);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private class NextEnumerator : IEnumerator<T>
{
private readonly SplittingEnumerable<T> splitter;
private readonly NextEnumerable parent;
private readonly IEnumerator<T> enumerator;
private T currentItem;
public NextEnumerator(SplittingEnumerable<T> splitter, NextEnumerable parent, IEnumerator<T> enumerator)
{
this.splitter = splitter;
this.parent = parent;
this.enumerator = enumerator;
}
public bool MoveNext()
{
this.parent.currentSize += 1;
this.currentItem = this.splitter.lastItem;
var hasCcurent = this.splitter.hasCurrent;
this.parent.IsComplete = this.parent.currentSize > this.splitter.maxSize;
if (this.parent.IsComplete)
{
return false;
}
if (hasCcurent)
{
var result = this.enumerator.MoveNext();
this.splitter.lastItem = this.enumerator.Current;
this.splitter.hasCurrent = result;
}
return hasCcurent;
}
public void Reset()
{
throw new System.NotImplementedException();
}
public T Current
{
get { return this.currentItem; }
}
object IEnumerator.Current
{
get { return this.Current; }
}
public void Dispose()
{
}
}
}
}
Tôi biết mọi người đã sử dụng các hệ thống phức tạp để thực hiện công việc này, và tôi thực sự không hiểu tại sao. Lấy và bỏ qua sẽ cho phép tất cả các thao tác đó bằng cách sử dụng lựa chọn chung với Func<TSource,Int32,TResult>
chức năng biến đổi. Giống:
public IEnumerable<IEnumerable<T>> Buffer<T>(IEnumerable<T> source, int size)=>
source.Select((item, index) => source.Skip(size * index).Take(size)).TakeWhile(bucket => bucket.Any());
source
sẽ được lặp lại rất thường xuyên.
Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
.
Chỉ cần thực hiện một dòng khác. Nó hoạt động ngay cả với một danh sách trống, trong trường hợp này bạn nhận được một bộ sưu tập lô kích thước bằng không.
var aList = Enumerable.Range(1, 100).ToList(); //a given list
var size = 9; //the wanted batch size
//number of batches are: (aList.Count() + size - 1) / size;
var batches = Enumerable.Range(0, (aList.Count() + size - 1) / size).Select(i => aList.GetRange( i * size, Math.Min(size, aList.Count() - i * size)));
Assert.True(batches.Count() == 12);
Assert.AreEqual(batches.ToList().ElementAt(0), new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
Assert.AreEqual(batches.ToList().ElementAt(1), new List<int>() { 10, 11, 12, 13, 14, 15, 16, 17, 18 });
Assert.AreEqual(batches.ToList().ElementAt(11), new List<int>() { 100 });
Một cách khác là sử dụng toán tử Rx Buffer
//using System.Linq;
//using System.Reactive.Linq;
//using System.Reactive.Threading.Tasks;
var observableBatches = anAnumerable.ToObservable().Buffer(size);
var batches = aList.ToObservable().Buffer(size).ToList().ToTask().GetAwaiter().GetResult();
GetAwaiter().GetResult()
. Đây là mùi mã cho mã đồng bộ bắt buộc phải gọi mã không đồng bộ.
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);
}