8000 Fix collection-optimized paths for `Batch` by viceroypenguin · Pull Request #965 · morelinq/MoreLINQ · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fix collection-optimized paths for Batch #965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 26, 2023
16 changes: 16 additions & 0 deletions MoreLinq.Test/BatchTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,22 @@ public void BatchEmptySource(SourceKind kind)
var batches = Enumerable.Empty<int>().ToSourceKind(kind).Batch(100);
Assert.That(batches, Is.Empty);
}

[TestCase(SourceKind.BreakingList)]
[TestCase(SourceKind.BreakingReadOnlyList)]
[TestCase(SourceKind.BreakingCollection)]
public void BatchUsesCollectionCountAtIterationTime(SourceKind kind)
{
var list = new List<int> { 1, 2 };
var result = list.AsSourceKind(kind).Batch(3);

list.Add(3);
result.AssertSequenceEqual(new[] { 1, 2, 3 });

list.Add(4);
// should fail trying to enumerate because count is now greater than the batch size
Assert.That(result.Consume, Throws.TypeOf<BreakException>());
}
}
}

Expand Down
17 changes: 13 additions & 4 deletions MoreLinq.Test/TestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,22 @@ internal static IEnumerable<string> GenerateSplits(this string str, params char[
}

internal static IEnumerable<T> ToSourceKind<T>(this IEnumerable<T> input, SourceKind sourceKind) =>
#pragma warning disable IDE0072 // Add missing cases
sourceKind switch
#pragma warning restore IDE0072 // Add missing cases
{
SourceKind.Sequence => input.Select(x => x),
SourceKind.BreakingList => new BreakingList<T>(input.ToList()),
SourceKind.BreakingReadOnlyList => new BreakingReadOnlyList<T>(input.ToList()),
SourceKind.BreakingCollection => new BreakingCollection<T>(input.ToList()),
SourceKind.BreakingReadOnlyCollection => new BreakingReadOnlyCollection<T>(input.ToList()),
var kind => input.ToList().AsSourceKind(kind)
};

internal static IEnumerable<T> AsSourceKind<T>(this List<T> input, SourceKind sourceKind) =>
sourceKind switch
{
SourceKind.Sequence => input.Select(x => x),
SourceKind.BreakingList => new BreakingList<T>(input),
SourceKind.BreakingReadOnlyList => new BreakingReadOnlyList<T>(input),
SourceKind.BreakingCollection => new BreakingCollection<T>(input),
SourceKind.BreakingReadOnlyCollection => new BreakingReadOnlyCollection<T>(input),
_ => throw new ArgumentException(null, nameof(sourceKind))
};
}
Expand Down
86 changes: 41 additions & 45 deletions MoreLinq/Batch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ namespace MoreLinq
{
using System;
using System.Collections.Generic;
using System.Linq;

static partial class MoreEnumerable
{
Expand Down Expand Up @@ -87,69 +86,66 @@ public static IEnumerable<TResult> Batch<TSource, TResult>(this IEnumerable<TSou
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));

switch (source)
return _(); IEnumerable<TResult> _()
{
case ICollection<TSource> { Count: 0 }:
switch (source)
{
return Enumerable.Empty<TResult>();
}
case ICollection<TSource> collection when collection.Count <= size:
{
return _(); IEnumerable<TResult> _()
case ICollection<TSource> { Count: 0 }:
{
break;
}
case ICollection<TSource> collection when collection.Count <= size:
{
var bucket = new TSource[collection.Count];
collection.CopyTo(bucket, 0);
yield return resultSelector(bucket);
break;
}
}
case IReadOnlyCollection<TSource> { Count: 0 }:
{
return Enumerable.Empty<TResult>();
}
case IReadOnlyList<TSource> list when list.Count <= size:
{
return _(); IEnumerable<TResult> _()
case IReadOnlyCollection<TSource> { Count: 0 }:
{
break;
}
case IReadOnlyList<TSource> list when list.Count <= size:
{
var bucket = new TSource[list.Count];
for (var i = 0; i < list.Count; i++)
bucket[i] = list[i];
yield return resultSelector(bucket);
break;
}
}
case IReadOnlyCollection<TSource> collection when collection.Count <= size:
{
return Batch(collection.Count);
}
default:
{
return Batch(size);
}
case IReadOnlyCollection<TSource> collection when collection.Count <= size:
{
size = collection.Count;
goto default;
}
default:
{
TSource[]? bucket = null;
var count = 0;

IEnumerable<TResult> Batch(int size)
{
TSource[]? bucket = null;
var count = 0;
foreach (var item in source)
{
bucket ??= new TSource[size];
bucket[count++] = item;

foreach (var item in source)
{
bucket ??= new TSource[size];
bucket[count++] = item;
// The bucket is fully buffered before it's yielded
if (count != size)
continue;

// The bucket is fully buffered before it's yielded
if (count != size)
continue;
yield return resultSelector(bucket);

yield return resultSelector(bucket);
bucket = null;
count = 0;
}

bucket = null;
count = 0;
}
// Return the last bucket with all remaining elements
if (count > 0)
{
Array.Resize(ref bucket, count);
yield return resultSelector(bucket);
}

// Return the last bucket with all remaining elements
if (count > 0)
{
Array.Resize(ref bucket, count);
yield return resultSelector(bucket);
break;
}
}
}
Expand Down
0