Skip to content

Commit

Permalink
feat: creating branch options
Browse files Browse the repository at this point in the history
  • Loading branch information
Farenheith committed Nov 15, 2024
1 parent 0e3a7a1 commit fb5c4af
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ namespace Codibre.EnumerableExtensions.Branching;
public sealed class AsyncBranchingBuilder<T>(IAsyncEnumerable<T> source) : BaseBranchingBuilder<T>
{
private static readonly LinkedNode<T>? _null = null;
internal override LinkedNode<T> Iterate(int branchCount)
internal override LinkedNode<T> Iterate(BranchRunOptions options)
{
var enumerator = source.GetAsyncEnumerator();
return new(enumerator.Current, new(
return LinkedNode<T>.Root(
async (c) => await enumerator.MoveNextAsync() ? new(enumerator.Current, c) : _null,
branchCount
));
options
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ internal BaseBranchingBuilder<T> Add(Func<IAsyncEnumerable<T>, Task> branch)
return this;
}

public async Task Run()
public async Task Run(BranchRunOptions? options = null)
{
var node = Iterate(_branches.Count);
var node = Iterate(options ?? BranchRunOptions.Default);
await Task.WhenAll(_branches.Select(x => x(node.GetBranchedIterable())));
}

internal abstract LinkedNode<T> Iterate(int branchCount);
internal abstract LinkedNode<T> Iterate(BranchRunOptions limit);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

namespace Codibre.EnumerableExtensions.Branching;

public readonly struct BranchRunOptions(int limit)
{
public static readonly BranchRunOptions Default = new(ushort.MaxValue / 4);
public int Limit => limit;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ namespace Codibre.EnumerableExtensions.Branching;

public sealed class BranchingBuilder<T>(IEnumerable<T> source) : BaseBranchingBuilder<T>
{
internal override LinkedNode<T> Iterate(int branchCount)
internal override LinkedNode<T> Iterate(BranchRunOptions options)
{
var enumerator = source.GetEnumerator();
return new(default!, new(
return LinkedNode<T>.Root(
(c) => new(enumerator.MoveNext() ? new LinkedNode<T>(enumerator.Current, c) : null),
branchCount
));
options
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Runtime.CompilerServices;
using System.Xml.XPath;

namespace Codibre.EnumerableExtensions.Branching.Internal;

internal sealed record AsyncBranchContext<T>(Func<IBranchContext<T>, ValueTask<LinkedNode<T>?>> GetNext)
: IBranchContext<T>
{

public ValueTask<LinkedNode<T>?> FillNext()
{
var result = GetNext(this);
return result.IsCompleted ? new(Task.Run(() => result.Result)) : result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@

namespace Codibre.EnumerableExtensions.Branching.Internal;

internal sealed record BranchContext<T>(Func<BranchContext<T>, ValueTask<LinkedNode<T>?>> GetNext, int _branchCount)
internal sealed record BranchContext<T>(Func<IBranchContext<T>, ValueTask<LinkedNode<T>?>> GetNext, BranchRunOptions options)
: IBranchContext<T>
{
private ushort _count = 0;
private readonly ushort _limit = ushort.MaxValue / 4;

internal ValueTask<LinkedNode<T>?> FillNext()
=> ++_count <= _limit ? GetNext(this) : GetYielded();
public ValueTask<LinkedNode<T>?> FillNext()
{
var result = GetNext(this);
if (result.IsCompleted) return ++_count <= options.Limit ? result : GetYielded(result.Result);
_count = 0;
return result;
}

private async ValueTask<LinkedNode<T>?> GetYielded()
private ValueTask<LinkedNode<T>?> GetYielded(LinkedNode<T>? result)
{
_count = 0;
await Task.Yield();
return await GetNext(this);
return new(Task.Run(() => result));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Runtime.CompilerServices;
using System.Xml.XPath;

namespace Codibre.EnumerableExtensions.Branching.Internal;

internal interface IBranchContext<T>
{
ValueTask<LinkedNode<T>?> FillNext();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
namespace Codibre.EnumerableExtensions.Branching.Internal;

internal sealed record LinkedNode<T>(T Value, BranchContext<T> Context)
internal sealed record LinkedNode<T>(T Value, IBranchContext<T> Context)
{
public Lazy<ValueTask<LinkedNode<T>?>> Next { get; } = new(Context.FillNext, LazyThreadSafetyMode.ExecutionAndPublication);

public static LinkedNode<T> Root(Func<IBranchContext<T>, ValueTask<LinkedNode<T>?>> getNext, BranchRunOptions options)
{
IBranchContext<T> context = options.Limit <= 1
? new AsyncBranchContext<T>(getNext)
: new BranchContext<T>(getNext, options);
return new(default!, context);
}
}
40 changes: 24 additions & 16 deletions test/Codibre.EnumerableExtensions.Branching.Benchmark/Benchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,30 @@ public static IEnumerable<int> AddOps(this IEnumerable<int> source, int value)

public class Benchmarks
{
[Params(100, 1000, 10000)]
[Params(100)] // , 1000, 10000)]
public int _size = 100;
private IEnumerable<int> GetBaseEnumerable()
=> Enumerable.Range(0, _size)
.AddOps(2)
.AddOps(3)
.AddOps(4);

[Benchmark]
public void Separate()
{
GetBaseEnumerable().Min();
GetBaseEnumerable().Max();
GetBaseEnumerable().Average();
}
// [Benchmark]
// public void Separate()
// {
// GetBaseEnumerable().Min();
// GetBaseEnumerable().Max();
// GetBaseEnumerable().Average();
// }

[Benchmark]
public void ManualBranch()
{
var baseEnum = GetBaseEnumerable();
baseEnum.Min();
baseEnum.Max();
baseEnum.Average();
}
// [Benchmark]
// public void ManualBranch()
// {
// var baseEnum = GetBaseEnumerable();
// baseEnum.Min();
// baseEnum.Max();
// baseEnum.Average();
// }

[Benchmark]
public Task Branching() => GetBaseEnumerable()
Expand All @@ -45,4 +45,12 @@ public Task Branching() => GetBaseEnumerable()
.Add(x => x.MaxAsync())
.Add(x => x.AverageAsync())
.Run();

[Benchmark]
public Task BranchingZeroLimit() => GetBaseEnumerable()
.Branch()
.Add(x => x.MinAsync())
.Add(x => x.MaxAsync())
.Add(x => x.AverageAsync())
.Run(new(1));
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,37 @@ await enumerable.Branch()
var refValue = steps[0];
steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total);
}

[Fact]
public async Task Should_Intercalate_The_Steps_Between_Every_Branch_When_Using_AsyncContextBranch()
{
// Arrange
var total = 100000;
var list = Enumerable.Range(0, total).ToAsyncEnumerable();
List<int> steps = [];
var enumerable = Op(list);

// Act
await enumerable.Branch()
.Add(x => x.Select((x) =>
{
steps.Add(1);
return x;
}).ToArrayAsync(), out var a)
.Add(x => x.Select((x) =>
{
steps.Add(2);
return x;
}).ToArrayAsync(), out var b)
.Add(x => x.Select((x) =>
{
steps.Add(3);
return x;
}).ToArrayAsync(), out var c)
.Run(new(1));

// Assert
var refValue = steps[0];
steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,37 @@ await enumerable.Branch()
var refValue = steps[0];
steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total);
}

[Fact]
public async Task Should_Intercalate_The_Steps_Between_Every_Branch_When_Using_AsyncContextBranch()
{
// Arrange
var total = 100000;
var list = Enumerable.Range(0, total);
List<int> steps = [];
var enumerable = Op(list);

// Act
await enumerable.Branch()
.Add(x => x.Select((x) =>
{
steps.Add(1);
return x;
}).ToArrayAsync(), out var a)
.Add(x => x.Select((x) =>
{
steps.Add(2);
return x;
}).ToArrayAsync(), out var b)
.Add(x => x.Select((x) =>
{
steps.Add(3);
return x;
}).ToArrayAsync(), out var c)
.Run(new(1));

// Assert
var refValue = steps[0];
steps.TakeWhile((x) => x == refValue).Count().Should().BeLessThan(total);
}
}

0 comments on commit fb5c4af

Please sign in to comment.