-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add sync .BindByIndex() * Add async .BindByIndex() * Add documentation
- Loading branch information
1 parent
1b8509b
commit b942b7f
Showing
7 changed files
with
471 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
namespace SuperLinq.Async; | ||
|
||
public static partial class AsyncSuperEnumerable | ||
{ | ||
/// <summary> | ||
/// Selects elements by index from a sequence. | ||
/// </summary> | ||
/// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam> | ||
/// <param name="source">The source sequence.</param> | ||
/// <param name="indices">The list of indices of elements in the <paramref name="source"/> sequence to select.</param> | ||
/// <returns> | ||
/// An <see cref="IAsyncEnumerable{T}"/> whose elements are the result of selecting elements according to the <paramref name="indices"/> sequence. | ||
/// </returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="indices"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentOutOfRangeException">An index in <paramref name="indices"/> is out of range for the input sequence <paramref name="source"/>.</exception> | ||
public static IAsyncEnumerable<TSource> BindByIndex<TSource>( | ||
this IAsyncEnumerable<TSource> source, | ||
IAsyncEnumerable<int> indices) | ||
{ | ||
#pragma warning disable MA0015 | ||
return BindByIndex(source, indices, static (e, i) => e, static i => throw new ArgumentOutOfRangeException(nameof(indices), "Index is greater than the length of the first sequence.")); | ||
#pragma warning restore MA0015 | ||
} | ||
|
||
/// <summary> | ||
/// Selects elements by index from a sequence and transforms them using the provided functions. | ||
/// </summary> | ||
/// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam> | ||
/// <typeparam name="TResult">The type of the elements of the resulting sequence.</typeparam> | ||
/// <param name="source">The source sequence.</param> | ||
/// <param name="indices">The list of indices of elements in the <paramref name="source"/> sequence to select.</param> | ||
/// <param name="resultSelector">A transform function to apply to each source element; the second parameter of the function represents the index of the output sequence.</param> | ||
/// <param name="missingSelector">A transform function to apply to missing source elements; the parameter represents the index of the output sequence.</param> | ||
/// <returns> | ||
/// An <see cref="IAsyncEnumerable{T}"/> whose elements are the result of selecting elements according to the <paramref name="indices"/> sequence | ||
/// and invoking the transform function. | ||
/// </returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="indices"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="resultSelector"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="missingSelector"/> is <see langword="null"/>.</exception> | ||
/// <remarks> | ||
/// <para> | ||
/// This method uses deferred execution and streams its results. | ||
/// </para> | ||
/// </remarks> | ||
public static IAsyncEnumerable<TResult> BindByIndex<TSource, TResult>( | ||
this IAsyncEnumerable<TSource> source, | ||
IAsyncEnumerable<int> indices, | ||
Func<TSource, int, TResult> resultSelector, | ||
Func<int, TResult> missingSelector) | ||
{ | ||
Guard.IsNotNull(source); | ||
Guard.IsNotNull(indices); | ||
Guard.IsNotNull(resultSelector); | ||
Guard.IsNotNull(missingSelector); | ||
|
||
return _(source, indices, resultSelector, missingSelector); | ||
|
||
static async IAsyncEnumerable<TResult> _( | ||
IAsyncEnumerable<TSource> source, IAsyncEnumerable<int> indices, Func<TSource, int, TResult> resultSelector, Func<int, TResult> missingSelector, | ||
[EnumeratorCancellation] CancellationToken cancellationToken = default) | ||
{ | ||
// keeps track of the order of indices to know what order items should be output in | ||
var lookup = await indices.Index().ToDictionaryAsync(x => { Guard.IsGreaterThanOrEqualTo(x.item, 0, nameof(indices)); return x.item; }, x => x.index, cancellationToken).ConfigureAwait(false); | ||
// keep track of items out of output order | ||
var lookback = new Dictionary<int, TSource>(); | ||
|
||
// which input index are we on? | ||
var index = 0; | ||
// which output index are we on? | ||
var outputIndex = 0; | ||
|
||
// for each item in input | ||
await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false)) | ||
{ | ||
// does the current input index have an output? | ||
if (lookup.TryGetValue(index, out var oi)) | ||
{ | ||
// is the current item's output order the next one? | ||
if (oi == outputIndex) | ||
{ | ||
// return the item and increment output order | ||
yield return resultSelector(item, outputIndex); | ||
outputIndex++; | ||
|
||
// while we're here, catch up on any lookbacks | ||
while (lookback.TryGetValue(outputIndex, out var e)) | ||
{ | ||
yield return resultSelector(e, outputIndex); | ||
lookback.Remove(outputIndex); | ||
outputIndex++; | ||
} | ||
} | ||
// otherwise, store in lookback for later | ||
else | ||
{ | ||
lookback[oi] = item; | ||
} | ||
} | ||
|
||
index++; | ||
} | ||
|
||
// catch up any remaining items | ||
while (outputIndex < lookup.Count) | ||
{ | ||
// can we find the current output index in lookback? | ||
if (lookback.TryGetValue(outputIndex, out var e)) | ||
{ | ||
// return it | ||
yield return resultSelector(e, outputIndex); | ||
lookback.Remove(outputIndex); | ||
} | ||
else | ||
{ | ||
// otherwise, return a missing item | ||
yield return missingSelector(outputIndex); | ||
} | ||
|
||
outputIndex++; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
namespace SuperLinq; | ||
|
||
public static partial class SuperEnumerable | ||
{ | ||
/// <summary> | ||
/// Selects elements by index from a sequence. | ||
/// </summary> | ||
/// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam> | ||
/// <param name="source">The source sequence.</param> | ||
/// <param name="indices">The list of indices of elements in the <paramref name="source"/> sequence to select.</param> | ||
/// <returns> | ||
/// An <see cref="IEnumerable{T}"/> whose elements are the result of selecting elements according to the <paramref name="indices"/> sequence. | ||
/// </returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="indices"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentOutOfRangeException">An index in <paramref name="indices"/> is out of range for the input sequence <paramref name="source"/>.</exception> | ||
public static IEnumerable<TSource> BindByIndex<TSource>( | ||
this IEnumerable<TSource> source, | ||
IEnumerable<int> indices) | ||
{ | ||
#pragma warning disable MA0015 | ||
return BindByIndex(source, indices, static (e, i) => e, static i => throw new ArgumentOutOfRangeException(nameof(indices), "Index is greater than the length of the first sequence.")); | ||
#pragma warning restore MA0015 | ||
} | ||
|
||
/// <summary> | ||
/// Selects elements by index from a sequence and transforms them using the provided functions. | ||
/// </summary> | ||
/// <typeparam name="TSource">The type of the elements of <paramref name="source"/>.</typeparam> | ||
/// <typeparam name="TResult">The type of the elements of the resulting sequence.</typeparam> | ||
/// <param name="source">The source sequence.</param> | ||
/// <param name="indices">The list of indices of elements in the <paramref name="source"/> sequence to select.</param> | ||
/// <param name="resultSelector">A transform function to apply to each source element; the second parameter of the function represents the index of the output sequence.</param> | ||
/// <param name="missingSelector">A transform function to apply to missing source elements; the parameter represents the index of the output sequence.</param> | ||
/// <returns> | ||
/// An <see cref="IEnumerable{T}"/> whose elements are the result of selecting elements according to the <paramref name="indices"/> sequence | ||
/// and invoking the transform function. | ||
/// </returns> | ||
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="indices"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="resultSelector"/> is <see langword="null"/>.</exception> | ||
/// <exception cref="ArgumentNullException"><paramref name="missingSelector"/> is <see langword="null"/>.</exception> | ||
/// <remarks> | ||
/// <para> | ||
/// This method uses deferred execution and streams its results. | ||
/// </para> | ||
/// </remarks> | ||
public static IEnumerable<TResult> BindByIndex<TSource, TResult>( | ||
this IEnumerable<TSource> source, | ||
IEnumerable<int> indices, | ||
Func<TSource, int, TResult> resultSelector, | ||
Func<int, TResult> missingSelector) | ||
{ | ||
Guard.IsNotNull(source); | ||
Guard.IsNotNull(indices); | ||
Guard.IsNotNull(resultSelector); | ||
Guard.IsNotNull(missingSelector); | ||
|
||
return _(source, indices, resultSelector, missingSelector); | ||
|
||
static IEnumerable<TResult> _(IEnumerable<TSource> source, IEnumerable<int> indices, Func<TSource, int, TResult> resultSelector, Func<int, TResult> missingSelector) | ||
{ | ||
// keeps track of the order of indices to know what order items should be output in | ||
var lookup = indices.Index().ToDictionary(x => { Guard.IsGreaterThanOrEqualTo(x.item, 0, nameof(indices)); return x.item; }, x => x.index); | ||
// keep track of items out of output order | ||
var lookback = new Dictionary<int, TSource>(); | ||
|
||
// which input index are we on? | ||
var index = 0; | ||
// which output index are we on? | ||
var outputIndex = 0; | ||
|
||
// for each item in input | ||
foreach (var item in source) | ||
{ | ||
// does the current input index have an output? | ||
if (lookup.TryGetValue(index, out var oi)) | ||
{ | ||
// is the current item's output order the next one? | ||
if (oi == outputIndex) | ||
{ | ||
// return the item and increment output order | ||
yield return resultSelector(item, outputIndex); | ||
outputIndex++; | ||
|
||
// while we're here, catch up on any lookbacks | ||
while (lookback.TryGetValue(outputIndex, out var e)) | ||
{ | ||
yield return resultSelector(e, outputIndex); | ||
lookback.Remove(outputIndex); | ||
outputIndex++; | ||
} | ||
} | ||
// otherwise, store in lookback for later | ||
else | ||
{ | ||
lookback[oi] = item; | ||
} | ||
} | ||
|
||
index++; | ||
} | ||
|
||
// catch up any remaining items | ||
while (outputIndex < lookup.Count) | ||
{ | ||
// can we find the current output index in lookback? | ||
if (lookback.TryGetValue(outputIndex, out var e)) | ||
{ | ||
// return it | ||
yield return resultSelector(e, outputIndex); | ||
lookback.Remove(outputIndex); | ||
} | ||
else | ||
{ | ||
// otherwise, return a missing item | ||
yield return missingSelector(outputIndex); | ||
} | ||
|
||
outputIndex++; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
namespace Test.Async; | ||
|
||
public class BindByIndexTest | ||
{ | ||
[Fact] | ||
public void BindByIndexIsLazy() | ||
{ | ||
new AsyncBreakingSequence<int>().BindByIndex(new AsyncBreakingSequence<int>()); | ||
new AsyncBreakingSequence<int>().BindByIndex(new AsyncBreakingSequence<int>(), BreakingFunc.Of<int, int, int>(), BreakingFunc.Of<int, int>()); | ||
} | ||
|
||
[Fact] | ||
public async Task BindByIndexDisposesEnumerators() | ||
{ | ||
await using var seq1 = TestingSequence.Of<int>(); | ||
await using var seq2 = TestingSequence.Of<int>(); | ||
await seq1.BindByIndex(seq2).AssertEmpty(); | ||
} | ||
|
||
[Fact] | ||
public Task BindByIndexInOrder() | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(1, 3, 5, 7, 9); | ||
|
||
return seq1.BindByIndex(seq2).AssertSequenceEqual(seq2.Select(x => x + 1)); | ||
} | ||
|
||
[Fact] | ||
public Task BindByIndexOutOfOrder() | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(9, 7, 5, 3, 1); | ||
|
||
return seq1.BindByIndex(seq2).AssertSequenceEqual(seq2.Select(x => x + 1)); | ||
} | ||
|
||
[Fact] | ||
public Task BindByIndexComplex() | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(0, 1, 8, 9, 3, 4, 2); | ||
|
||
return seq1.BindByIndex(seq2).AssertSequenceEqual(seq2.Select(x => x + 1)); | ||
} | ||
|
||
[Theory] | ||
[InlineData(-1)] | ||
[InlineData(10)] | ||
[InlineData(100)] | ||
public Task BindByIndexThrowExceptionInvalidIndex(int index) | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(index); | ||
|
||
return Assert.ThrowsAsync<ArgumentOutOfRangeException>("indices", | ||
async () => await seq1.BindByIndex(seq2).Consume()); | ||
} | ||
|
||
[Fact] | ||
public Task BindByIndexTransformInOrder() | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(1, 3, 5, 7, 9); | ||
|
||
return seq1.BindByIndex(seq2, (e, i) => e, i => default(int?)).AssertSequenceEqual(seq2.Select(x => (int?)(x + 1))); | ||
} | ||
|
||
[Fact] | ||
public Task BindByIndexTransformOutOfOrder() | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(9, 7, 5, 3, 1); | ||
|
||
return seq1.BindByIndex(seq2, (e, i) => e, i => default(int?)).AssertSequenceEqual(seq2.Select(x => (int?)(x + 1))); | ||
} | ||
|
||
[Fact] | ||
public Task BindByIndexTransformComplex() | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(0, 1, 8, 9, 3, 4, 2); | ||
|
||
return seq1.BindByIndex(seq2, (e, i) => e, i => default(int?)).AssertSequenceEqual(seq2.Select(x => (int?)(x + 1))); | ||
} | ||
|
||
[Fact] | ||
public Task BindByIndexTransformInvalidIndex() | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(1, 10, 3, 30); | ||
|
||
return seq1.BindByIndex(seq2, (e, i) => e, i => default(int?)).AssertSequenceEqual(2, null, 4, null); | ||
} | ||
|
||
[Fact] | ||
public Task BindByIndexTransformThrowExceptionNegativeIndex() | ||
{ | ||
var seq1 = AsyncEnumerable.Range(1, 10); | ||
var seq2 = AsyncSeq(-1); | ||
|
||
return Assert.ThrowsAsync<ArgumentOutOfRangeException>("indices", | ||
async () => await seq1.BindByIndex(seq2, (e, i) => e, i => default(int?)).Consume()); | ||
} | ||
} |
Oops, something went wrong.