มีใครแนะนำวิธีสร้างแบทช์ที่มีขนาดบางขนาดใน linq ได้ไหม
ตามหลักการแล้วฉันต้องการที่จะสามารถดำเนินการเป็นกลุ่มของจำนวนที่กำหนดได้
มีใครแนะนำวิธีสร้างแบทช์ที่มีขนาดบางขนาดใน linq ได้ไหม
ตามหลักการแล้วฉันต้องการที่จะสามารถดำเนินการเป็นกลุ่มของจำนวนที่กำหนดได้
คำตอบ:
คุณไม่จำเป็นต้องเขียนโค้ดใด ๆ ใช้เมธอด MoreLINQ Batch ซึ่งแบ่งลำดับแหล่งที่มาเป็นกลุ่มขนาด (MoreLINQ พร้อมใช้งานเป็นแพ็คเกจ 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).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));
}
}
และการใช้งานจะเป็น:
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));
}
เอาท์พุท:
0,1,2
3,4,5
6,7,8
9
GroupBy
เริ่มการแจงนับแล้วไม่จำเป็นต้องระบุแหล่งที่มาอย่างครบถ้วนหรือไม่? สิ่งนี้ทำให้สูญเสียการประเมินแหล่งที่มาอย่างเกียจคร้านดังนั้นในบางกรณีผลประโยชน์ทั้งหมดของการแบทช์!
หากคุณเริ่มต้นด้วยsequence
กำหนดเป็น an IEnumerable<T>
และคุณรู้ว่าสามารถแจกแจงหลาย ๆ ครั้งได้อย่างปลอดภัย (เช่นเพราะเป็นอาร์เรย์หรือรายการ) คุณสามารถใช้รูปแบบง่ายๆนี้เพื่อประมวลผลองค์ประกอบในแบทช์:
while (sequence.Any())
{
var batch = sequence.Take(10);
sequence = sequence.Skip(10);
// do whatever you need to do with each batch here
}
ทั้งหมดที่กล่าวมาทำงานได้แย่มากกับแบทช์ขนาดใหญ่หรือพื้นที่หน่วยความจำเหลือน้อย ต้องเขียนของฉันเองที่จะไปป์ไลน์ (สังเกตว่าไม่มีการสะสมไอเท็มที่ใดก็ได้):
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())
นี่คือการใช้งาน Batch แบบหนึ่งฟังก์ชั่นที่ขี้เกียจอย่างเต็มที่และต่ำซึ่งไม่ได้ทำการสะสมใด ๆ ขึ้นอยู่กับ (และแก้ไขปัญหาใน) โซลูชันของ Nick Whaley ด้วยความช่วยเหลือจาก EricRoller
การวนซ้ำมาจาก IE ที่เป็นพื้นฐานโดยตรงดังนั้นจึงต้องมีการแจกแจงองค์ประกอบตามลำดับที่เข้มงวดและเข้าถึงได้ไม่เกินหนึ่งครั้ง หากองค์ประกอบบางอย่างไม่ถูกใช้ไปในวงในองค์ประกอบเหล่านั้นจะถูกทิ้ง (และพยายามเข้าถึงอีกครั้งผ่านตัววนซ้ำที่บันทึกไว้จะถูกโยนทิ้งInvalidOperationException: Enumeration already finished.
)
คุณสามารถทดสอบตัวอย่างที่สมบูรณ์.NET ซอ
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
โดยเพียงแค่เสมอเรียกหลังจากe.Count()
yield return e
คุณจะต้องจัดเรียงห่วงใน BatchInner ที่จะไม่ก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนดถ้าsource.Current
i >= size
ซึ่งจะช่วยขจัดความจำเป็นในการจัดสรรใหม่BatchInner
สำหรับแต่ละชุด
i
ดังนั้นสิ่งนี้จึงไม่จำเป็นต้องมีประสิทธิภาพมากกว่าการกำหนดคลาสแยกต่างหาก แต่ฉันคิดว่ามันสะอาดกว่าเล็กน้อย
ฉันสงสัยว่าทำไมไม่มีใครเคยโพสต์โซลูชัน for-loop ของโรงเรียนเก่า นี่คือหนึ่ง:
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);
}
ความเรียบง่ายนี้เกิดขึ้นได้เนื่องจากวิธี Take:
... แจกแจง
source
และให้องค์ประกอบจนกว่าcount
องค์ประกอบจะได้รับหรือsource
ไม่มีองค์ประกอบอีกต่อไป หากcount
เกินจำนวนองค์ประกอบในsource
องค์ประกอบทั้งหมดsource
จะถูกส่งกลับ
คำเตือน:
การใช้ Skip and Take ภายในลูปหมายความว่าจะมีการแจกแจงหลายครั้ง สิ่งนี้เป็นอันตรายหากการแจงนับถูกเลื่อนออกไป อาจส่งผลให้มีการดำเนินการแบบสอบถามฐานข้อมูลหรือคำขอทางเว็บหรืออ่านไฟล์หลายครั้ง ตัวอย่างนี้มีไว้อย่างชัดเจนสำหรับการใช้รายการที่ไม่ได้เลื่อนออกไปดังนั้นจึงมีปัญหาน้อยกว่า ยังคงเป็นวิธีแก้ปัญหาที่ช้าเนื่องจากการข้ามจะระบุคอลเล็กชันทุกครั้งที่มีการเรียกใช้
สิ่งนี้สามารถแก้ไขได้โดยใช้GetRange
วิธีการนี้ แต่ต้องมีการคำนวณเพิ่มเติมเพื่อแยกชุดที่เหลือที่เป็นไปได้:
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);
}
นี่คือวิธีที่สามในการจัดการสิ่งนี้ซึ่งใช้ได้กับ 2 ลูป เพื่อให้แน่ใจว่าคอลเลกชันจะถูกแจกแจงเพียง 1 ครั้ง!:
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
และTake
ภายในลูปหมายความว่าจะมีการแจกแจงหลายครั้ง สิ่งนี้เป็นอันตรายหากการแจงนับถูกเลื่อนออกไป อาจส่งผลให้มีการดำเนินการแบบสอบถามฐานข้อมูลหรือคำขอทางเว็บหรืออ่านไฟล์หลายครั้ง ในตัวอย่างของคุณคุณมีปัญหาList
ที่ไม่รอการตัดบัญชีดังนั้นจึงมีปัญหาน้อยกว่า
แนวทางเดียวกับ MoreLINQ แต่ใช้ List แทน Array ฉันยังไม่ได้ทำการเปรียบเทียบ แต่ความสามารถในการอ่านมีความสำคัญมากกว่าสำหรับบางคน:
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
พารามิเตอร์ไปยังของคุณnew List
เพื่อปรับขนาดให้เหมาะสม
batch.Clear();
ด้วยbatch = new List<T>();
นี่คือความพยายามในการปรับปรุงการใช้งานที่ขี้เกียจของ Nick Whaley ( ลิงค์ ) และ infogulch ( ลิงค์ ) Batch
อันนี้เข้มงวด คุณระบุแบตช์ตามลำดับที่ถูกต้องหรือคุณได้รับข้อยกเว้น
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++;
}
}
}
}
และนี่คือขี้เกียจการดำเนินงานสำหรับแหล่งที่มาของประเภทBatch
IList<T>
ข้อนี้ไม่มีข้อ จำกัด ในการแจงนับ แบทช์สามารถแจกแจงได้บางส่วนตามลำดับใด ๆ และมากกว่าหนึ่งครั้ง ข้อ จำกัด ในการไม่แก้ไขคอลเลกชันระหว่างการแจงนับยังคงมีอยู่ สิ่งนี้ทำได้โดยการโทรหลอกenumerator.MoveNext()
ก่อนที่จะให้ชิ้นส่วนหรือองค์ประกอบใด ๆ ข้อเสียคือตัวนับถูกปล่อยทิ้งไว้โดยไม่เปิดเผยเนื่องจากไม่ทราบว่าการแจงนับจะเสร็จสิ้นเมื่อใด
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];
}
}
}
ดังนั้นเมื่อเปิดหมวกที่ใช้งานได้สิ่งนี้จึงดูไม่สำคัญ .... แต่ใน C # มีข้อเสียที่สำคัญบางประการ
คุณอาจมองว่านี่เป็นส่วนขยายของ IEnumerable (Google และคุณอาจจะจบลงด้วยเอกสาร Haskell บางฉบับ แต่อาจมีบางสิ่งที่ F # ใช้แฉถ้าคุณรู้จัก F # ให้เหล่ที่เอกสาร Haskell และมันจะทำให้ ความรู้สึก).
Unfold เกี่ยวข้องกับการพับ ("รวม") ยกเว้นแทนที่จะวนซ้ำผ่านอินพุต IEnumerable มันจะวนซ้ำผ่านโครงสร้างข้อมูลเอาต์พุต (ความสัมพันธ์ที่คล้ายกันระหว่าง IEnumerable และ IObservable ฉันคิดว่า IObservable ใช้ "แฉ" ที่เรียกว่าสร้าง .. )
ก่อนอื่นคุณต้องมีวิธีการแฉฉันคิดว่ามันใช้ได้ (น่าเสียดายที่มันจะระเบิดสแต็กสำหรับ "รายการ" ขนาดใหญ่ ... คุณสามารถเขียนสิ่งนี้ได้อย่างปลอดภัยใน F # โดยใช้ผลตอบแทน! แทนที่จะเป็น 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)));
}
นี่เป็นสิ่งที่ป้านเล็กน้อยเนื่องจาก C # ไม่ได้ใช้บางสิ่งที่ใช้ภาษาที่ใช้งานได้ ... แต่โดยทั่วไปแล้วจะใช้เมล็ดพันธุ์จากนั้นสร้างคำตอบ "อาจจะ" ขององค์ประกอบถัดไปใน IEnumerable และเมล็ดพันธุ์ถัดไป (อาจ ไม่มีอยู่ใน C # ดังนั้นเราจึงใช้ IE ไม่สามารถปลอมได้) และเชื่อมต่อคำตอบที่เหลือ (ฉันไม่สามารถรับรองความซับซ้อน "O (n?)" ของสิ่งนี้ได้)
เมื่อคุณทำเสร็จแล้ว
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);
}
ทุกอย่างดูค่อนข้างสะอาด ... คุณใช้องค์ประกอบ "n" เป็นองค์ประกอบ "ถัดไป" ใน IEnumerable และ "tail" คือส่วนที่เหลือของรายการที่ยังไม่ได้ประมวลผล
ถ้าไม่มีอะไรอยู่ในหัว ... คุณจบแล้ว ... คุณกลับ "Nothing" (แต่แกล้งทำเป็น IE ว่างเปล่า>) ... มิฉะนั้นคุณจะส่งคืนองค์ประกอบส่วนหัวและส่วนท้ายเพื่อประมวลผล
คุณอาจทำได้โดยใช้ IObservable อาจมีเมธอดแบบ "แบตช์" อยู่แล้วและคุณอาจใช้วิธีนั้นได้
หากความเสี่ยงของสแต็กล้นทำให้กังวล (อาจเป็นไปได้) คุณควรติดตั้งใน F # (และอาจมีไลบรารี F # (FSharpX?) อยู่แล้วด้วย)
(ฉันได้ทำการทดสอบพื้นฐานบางอย่างเท่านั้นดังนั้นอาจมีข้อบกพร่องแปลก ๆ อยู่ในนั้น)
Maybe
สามารถมีอยู่ใน C # - ดูตัวอย่างOption
ใน Language Ext.
ฉันมาช้ามาก แต่ฉันพบสิ่งที่น่าสนใจกว่านี้
ดังนั้นเราสามารถใช้ที่นี่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 รายการ การวนซ้ำใช้เวลามากขึ้นเท่านั้นในกรณีของ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
วินาที (Take and Skip One) - 00: 00: 00.0008, 00: 00: 00.0008
GroupBy
แจกแจงอย่างครบถ้วนก่อนที่จะสร้างแถวเดียว นี่ไม่ใช่วิธีที่ดีในการทำแบทช์
foreach (var batch in Ids2.Batch(5000))
เป็นvar gourpBatch = Ids2.Batch(5000)
และตรวจสอบผลลัพธ์ตามกำหนดเวลา หรือเพิ่มสิ่งที่ต้องทำเพื่อvar SecBatch = Ids2.Batch2(StartIndex, BatchSize);
ฉันจะสนใจถ้าผลลัพธ์ของคุณสำหรับเวลาเปลี่ยน
ฉันเขียนการใช้งาน IEnumerable แบบกำหนดเองซึ่งทำงานโดยไม่ใช้ linq และรับประกันการแจงนับข้อมูลเพียงครั้งเดียว นอกจากนี้ยังดำเนินการทั้งหมดนี้โดยไม่ต้องสำรองรายการหรืออาร์เรย์ที่ทำให้หน่วยความจำระเบิดในชุดข้อมูลขนาดใหญ่
นี่คือการทดสอบพื้นฐานบางส่วน:
[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);
}
วิธีการขยายเพื่อแบ่งพาร์ติชันข้อมูล
/// <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);
}
}
นี่คือคลาสการใช้งาน
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()
{
}
}
}
}
การใช้งานอีกบรรทัดเดียว ใช้งานได้แม้จะมีรายการว่างในกรณีนี้คุณจะได้รับคอลเลกชันแบทช์ขนาดศูนย์
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 });
อีกวิธีหนึ่งคือการใช้ตัวดำเนินการ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()
คุณไม่ควรจะต้องใช้ นี่คือกลิ่นรหัสสำหรับรหัสซิงโครนัสที่เรียกรหัส async
เวอร์ชันที่ใช้งานง่ายและเข้าใจ
public static List<List<T>> chunkList<T>(List<T> listToChunk, int batchSize)
{
List<List<T>> batches = new List<List<T>>();
if (listToChunk.Count == 0) return batches;
bool moreRecords = true;
int fromRecord = 0;
int countRange = 0;
if (listToChunk.Count >= batchSize)
{
countRange = batchSize;
}
else
{
countRange = listToChunk.Count;
}
while (moreRecords)
{
List<T> batch = listToChunk.GetRange(fromRecord, countRange);
batches.Add(batch);
if ((fromRecord + batchSize) >= listToChunk.Count)
{
moreRecords = false;
}
fromRecord = fromRecord + batch.Count;
if ((fromRecord + batchSize) > listToChunk.Count)
{
countRange = listToChunk.Count - fromRecord;
}
else
{
countRange = batchSize;
}
}
return batches;
}
ฉันรู้ว่าทุกคนใช้ระบบที่ซับซ้อนในการทำงานนี้และฉันก็ไม่เข้าใจว่าทำไม Take and skip จะอนุญาตให้ดำเนินการทั้งหมดโดยใช้การเลือกร่วมกับFunc<TSource,Int32,TResult>
ฟังก์ชัน transform ชอบ:
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
จะมีการทำซ้ำบ่อยมาก
Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
.
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);
}