From bd885f26181e1a51aadd434ccd27e114c898a044 Mon Sep 17 00:00:00 2001 From: Leandro Fernandes Vieira Date: Fri, 19 Nov 2021 22:02:40 -0300 Subject: [PATCH 1/2] add impl and test --- MoreLinq.Test/BatchTest.cs | 40 ++++++++++++++++++++++++++++++++-- MoreLinq/Batch.cs | 44 +++++++++++++++++++++++++++++++++----- MoreLinq/Extensions.g.cs | 30 ++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/MoreLinq.Test/BatchTest.cs b/MoreLinq.Test/BatchTest.cs index c5c9186e7..27c701d5d 100644 --- a/MoreLinq.Test/BatchTest.cs +++ b/MoreLinq.Test/BatchTest.cs @@ -26,14 +26,14 @@ public class BatchTest [Test] public void BatchZeroSize() { - AssertThrowsArgument.OutOfRangeException("size",() => + AssertThrowsArgument.OutOfRangeException("size", () => new object[0].Batch(0)); } [Test] public void BatchNegativeSize() { - AssertThrowsArgument.OutOfRangeException("size",() => + AssertThrowsArgument.OutOfRangeException("size", () => new object[0].Batch(-1)); } @@ -61,6 +61,42 @@ public void BatchUnevenlyDivisibleSequence() reader.ReadEnd(); } + [Test] + public void BatchFactoryUnevenlyDivisibleSequence() + { + int size = 4; + int requested = 0; + int[] temp = null; + + var result = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.Batch(size, + x => x.Take(requested), + i => + { + requested = i; + + return temp ??= new int[size]; + }); + + using var reader = result.Read(); + + var first = reader.Read(); + first.AssertSequenceEqual(1, 2, 3, 4); + first.AssertSequenceEqual(temp); + + var second = reader.Read(); + second.AssertSequenceEqual(5, 6, 7, 8); + second.AssertSequenceEqual(temp); + + var third = reader.Read(); + third.AssertSequenceEqual(9); + + first.AssertSequenceEqual(9, 6, 7, 8); + second.AssertSequenceEqual(9, 6, 7, 8); + temp.AssertSequenceEqual(9, 6, 7, 8); + + reader.ReadEnd(); + } + [Test] public void BatchSequenceTransformingResult() { diff --git a/MoreLinq/Batch.cs b/MoreLinq/Batch.cs index 4621f46cf..5b235d272 100644 --- a/MoreLinq/Batch.cs +++ b/MoreLinq/Batch.cs @@ -80,10 +80,43 @@ public static IEnumerable> Batch(this IEnumerable< public static IEnumerable Batch(this IEnumerable source, int size, Func, TResult> resultSelector) + { + return Batch(source, size, resultSelector, size => new TSource[size]); + } + + /// + /// Batches the source sequence into sized buckets and applies a projection to each bucket. + /// + /// Type of elements in sequence. + /// Type of result returned by . + /// The source sequence. + /// Size of buckets. + /// The projection to apply to each bucket. + /// A function that receive the requested size for the next bucket and return the array where elements will be placed. + /// A sequence of projections on equally sized buckets containing elements of the source collection. + /// + /// This operator uses deferred execution and streams its results + /// (buckets are streamed but their content buffered). + /// + /// + /// When more than one bucket is streamed, all buckets except the last + /// is guaranteed to have elements. The last + /// bucket may be smaller depending on the remaining elements in the + /// sequence. + /// Each bucket is pre-allocated to elements. + /// If is set to a very large value, e.g. + /// to effectively disable batching by just + /// hoping for a single bucket, then it can lead to memory exhaustion + /// (). + /// + + public static IEnumerable Batch(this IEnumerable source, int size, + Func, TResult> resultSelector, Func bucketFactory) { if (source == null) throw new ArgumentNullException(nameof(source)); if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); + if (bucketFactory == null) throw new ArgumentNullException(nameof(bucketFactory)); switch (source) { @@ -95,7 +128,7 @@ public static IEnumerable Batch(this IEnumerable _() { - var bucket = new TSource[collection.Count]; + var bucket = bucketFactory(collection.Count); collection.CopyTo(bucket, 0); yield return resultSelector(bucket); } @@ -108,7 +141,7 @@ public static IEnumerable Batch(this IEnumerable _() { - var bucket = new TSource[list.Count]; + var bucket = bucketFactory(list.Count); for (var i = 0; i < list.Count; i++) bucket[i] = list[i]; yield return resultSelector(bucket); @@ -130,7 +163,7 @@ IEnumerable Batch(int size) foreach (var item in source) { - bucket ??= new TSource[size]; + bucket ??= bucketFactory(size); bucket[count++] = item; // The bucket is fully buffered before it's yielded @@ -146,8 +179,9 @@ IEnumerable Batch(int size) // Return the last bucket with all remaining elements if (bucket != null && count > 0) { - Array.Resize(ref bucket, count); - yield return resultSelector(bucket); + var newArray = bucketFactory(count); + Array.Copy(bucket, 0, newArray, 0, count); + yield return resultSelector(newArray); } } } diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index bfae169ac..f5a8412c9 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -710,6 +710,36 @@ public static IEnumerable Batch(this IEnumerable, TResult> resultSelector) => MoreEnumerable.Batch(source, size, resultSelector); + /// + /// Batches the source sequence into sized buckets and applies a projection to each bucket. + /// + /// Type of elements in sequence. + /// Type of result returned by . + /// The source sequence. + /// Size of buckets. + /// The projection to apply to each bucket. + /// A function that receive the requested size for the next bucket and return the array where elements will be placed. + /// A sequence of projections on equally sized buckets containing elements of the source collection. + /// + /// This operator uses deferred execution and streams its results + /// (buckets are streamed but their content buffered). + /// + /// + /// When more than one bucket is streamed, all buckets except the last + /// is guaranteed to have elements. The last + /// bucket may be smaller depending on the remaining elements in the + /// sequence. + /// Each bucket is pre-allocated to elements. + /// If is set to a very large value, e.g. + /// to effectively disable batching by just + /// hoping for a single bucket, then it can lead to memory exhaustion + /// (). + /// + + public static IEnumerable Batch(this IEnumerable source, int size, + Func, TResult> resultSelector, Func bucketFactory) + => MoreEnumerable.Batch(source, size, resultSelector, bucketFactory); + } /// Cartesian extension. From f7145aef365aedae2c42c14308e72a3e8e326821 Mon Sep 17 00:00:00 2001 From: Leandro Fernandes Vieira Date: Fri, 19 Nov 2021 23:06:41 -0300 Subject: [PATCH 2/2] rename variable tests --- MoreLinq.Test/BatchTest.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/MoreLinq.Test/BatchTest.cs b/MoreLinq.Test/BatchTest.cs index 27c701d5d..a629f343e 100644 --- a/MoreLinq.Test/BatchTest.cs +++ b/MoreLinq.Test/BatchTest.cs @@ -64,15 +64,14 @@ public void BatchUnevenlyDivisibleSequence() [Test] public void BatchFactoryUnevenlyDivisibleSequence() { - int size = 4; - int requested = 0; + int lastSize = 0; int[] temp = null; - var result = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.Batch(size, - x => x.Take(requested), - i => + var result = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.Batch(4, + x => x.Take(lastSize), + size => { - requested = i; + lastSize = size; return temp ??= new int[size]; });