Skip to content

Commit

Permalink
Improve performance of ScanRight
Browse files Browse the repository at this point in the history
  • Loading branch information
viceroypenguin committed Jan 23, 2023
1 parent 75b3808 commit 6298c03
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 79 deletions.
95 changes: 50 additions & 45 deletions Source/SuperLinq.Async/ScanRight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,86 +3,89 @@
public static partial class AsyncSuperEnumerable
{
/// <summary>
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements.
/// This operator is the right-associative version of the
/// <see cref="AsyncEnumerableEx.Scan{TSource}(IAsyncEnumerable{TSource}, Func{TSource, TSource, TSource})"/> LINQ operator.
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements. This operator is the
/// right-associative version of the <see cref="AsyncEnumerableEx.Scan{TSource}(IAsyncEnumerable{TSource},
/// Func{TSource, TSource, TSource})"/> LINQ operator.
/// </summary>
/// <typeparam name="TSource">Type of elements in source sequence.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="func">
/// A right-associative accumulator function to be invoked on each element.
/// Its first argument is the current value in the sequence; second argument is the previous accumulator value.
/// A right-associative accumulator function to be invoked on each element. Its first argument is the current value
/// in the sequence; second argument is the previous accumulator value.
/// </param>
/// <returns>The scanned sequence.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/> or <paramref name="func"/> is <see langword="null"/>.
/// </exception>
/// <example>
/// <code><![CDATA[
/// var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})");
/// <code><![CDATA[ var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})");
/// ]]></code>
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]</c>.
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5"
/// ]</c>.
/// </example>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// Source sequence is consumed greedily when an iteration of the resulting sequence begins.
/// This operator uses deferred execution and streams its results. Source sequence is consumed greedily when an
/// iteration of the resulting sequence begins.
/// </remarks>
public static IAsyncEnumerable<TSource> ScanRight<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
{
return source.ScanRight((a, b, ct) => new ValueTask<TSource>(func(a, b)));
}

/// <summary>
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements.
/// This operator is the right-associative version of the
/// <see cref="AsyncEnumerableEx.Scan{TSource}(IAsyncEnumerable{TSource}, Func{TSource, TSource, TSource})"/> LINQ operator.
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements. This operator is the
/// right-associative version of the <see cref="AsyncEnumerableEx.Scan{TSource}(IAsyncEnumerable{TSource},
/// Func{TSource, TSource, TSource})"/> LINQ operator.
/// </summary>
/// <typeparam name="TSource">Type of elements in source sequence.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="func">
/// A right-associative accumulator function to be invoked on each element.
/// Its first argument is the current value in the sequence; second argument is the previous accumulator value.
/// A right-associative accumulator function to be invoked on each element. Its first argument is the current value
/// in the sequence; second argument is the previous accumulator value.
/// </param>
/// <returns>The scanned sequence.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/>, <paramref name="func"/> is <see langword="null"/>.
/// </exception>
/// <example>
/// <code><![CDATA[
/// var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})");
/// <code><![CDATA[ var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})");
/// ]]></code>
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]</c>.
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5"
/// ]</c>.
/// </example>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// Source sequence is consumed greedily when an iteration of the resulting sequence begins.
/// This operator uses deferred execution and streams its results. Source sequence is consumed greedily when an
/// iteration of the resulting sequence begins.
/// </remarks>
public static IAsyncEnumerable<TSource> ScanRight<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, TSource, ValueTask<TSource>> func)
{
return source.ScanRight((a, b, ct) => func(a, b));
}

/// <summary>
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements.
/// This operator is the right-associative version of the
/// <see cref="AsyncEnumerableEx.Scan{TSource}(IAsyncEnumerable{TSource}, Func{TSource, TSource, TSource})"/> LINQ operator.
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements. This operator is the
/// right-associative version of the <see cref="AsyncEnumerableEx.Scan{TSource}(IAsyncEnumerable{TSource},
/// Func{TSource, TSource, TSource})"/> LINQ operator.
/// </summary>
/// <typeparam name="TSource">Type of elements in source sequence.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="func">
/// A right-associative accumulator function to be invoked on each element.
/// Its first argument is the current value in the sequence; second argument is the previous accumulator value.
/// A right-associative accumulator function to be invoked on each element. Its first argument is the current value
/// in the sequence; second argument is the previous accumulator value.
/// </param>
/// <returns>The scanned sequence.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/> or <paramref name="func"/> is <see langword="null"/>.
/// </exception>
/// <example>
/// <code><![CDATA[
/// var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})");
/// <code><![CDATA[ var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})");
/// ]]></code>
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]</c>.
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5"
/// ]</c>.
/// </example>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// Source sequence is consumed greedily when an iteration of the resulting sequence begins.
/// This operator uses deferred execution and streams its results. Source sequence is consumed greedily when an
/// iteration of the resulting sequence begins.
/// </remarks>
public static IAsyncEnumerable<TSource> ScanRight<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, TSource, CancellationToken, ValueTask<TSource>> func)
{
Expand All @@ -93,18 +96,18 @@ public static IAsyncEnumerable<TSource> ScanRight<TSource>(this IAsyncEnumerable

static async IAsyncEnumerable<TSource> _(IAsyncEnumerable<TSource> source, Func<TSource, TSource, CancellationToken, ValueTask<TSource>> func, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var e = source.Reverse().GetConfiguredAsyncEnumerator(cancellationToken);
var list = await source.ToListAsync(cancellationToken).ConfigureAwait(false);

if (!await e.MoveNextAsync())
if (list.Count == 0)
yield break;

var seed = e.Current;
var stack = new Stack<TSource>();
var seed = list[^1];
var stack = new Stack<TSource>(list.Count);
stack.Push(seed);

while (await e.MoveNextAsync())
for (var i = list.Count - 2; i >= 0; i--)
{
seed = await func(e.Current, seed, cancellationToken).ConfigureAwait(false);
seed = await func(list[i], seed, cancellationToken).ConfigureAwait(false);
stack.Push(seed);
}

Expand Down Expand Up @@ -207,12 +210,14 @@ public static IAsyncEnumerable<TAccumulate> ScanRight<TSource, TAccumulate>(this

static async IAsyncEnumerable<TAccumulate> _(IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TSource, TAccumulate, CancellationToken, ValueTask<TAccumulate>> func, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var stack = new Stack<TAccumulate>();
var list = await source.ToListAsync(cancellationToken).ConfigureAwait(false);

var stack = new Stack<TAccumulate>(list.Count + 1);
stack.Push(seed);

await foreach (var i in source.Reverse().WithCancellation(cancellationToken).ConfigureAwait(false))
for (var i = list.Count - 1; i >= 0; i--)
{
seed = await func(i, seed, cancellationToken).ConfigureAwait(false);
seed = await func(list[i], seed, cancellationToken).ConfigureAwait(false);
stack.Push(seed);
}

Expand Down
69 changes: 35 additions & 34 deletions Source/SuperLinq/ScanRight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,31 @@
public static partial class SuperEnumerable
{
/// <summary>
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements.
/// This operator is the right-associative version of the
/// <see cref="EnumerableEx.Scan{TSource}(IEnumerable{TSource}, Func{TSource, TSource, TSource})"/> LINQ operator.
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements. This operator is the
/// right-associative version of the <see cref="EnumerableEx.Scan{TSource}(IEnumerable{TSource}, Func{TSource,
/// TSource, TSource})"/> LINQ operator.
/// </summary>
/// <typeparam name="TSource">Type of elements in source sequence.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="func">
/// A right-associative accumulator function to be invoked on each element.
/// Its first argument is the current value in the sequence; second argument is the previous accumulator value.
/// A right-associative accumulator function to be invoked on each element. Its first argument is the current value
/// in the sequence; second argument is the previous accumulator value.
/// </param>
/// <returns>The scanned sequence.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/> or <paramref name="func"/> is <see langword="null"/>.
/// </exception>
/// <example>
/// <code><![CDATA[
/// <code><![CDATA[
/// var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})");
/// ]]></code>
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]</c>.
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5"
/// ]</c>.
/// </example>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// Source sequence is consumed greedily when an iteration of the resulting sequence begins.
/// This operator uses deferred execution and streams its results. Source sequence is consumed greedily when an
/// iteration of the resulting sequence begins.
/// </remarks>

public static IEnumerable<TSource> ScanRight<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
{
Guard.IsNotNull(source);
Expand All @@ -36,18 +37,18 @@ public static IEnumerable<TSource> ScanRight<TSource>(this IEnumerable<TSource>

static IEnumerable<TSource> _(IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
{
using var e = source.Reverse().GetEnumerator();
var list = source is IList<TSource> l ? l : source.ToList();

if (!e.MoveNext())
if (list.Count == 0)
yield break;

var seed = e.Current;
var stack = new Stack<TSource>();
var seed = list[^1];
var stack = new Stack<TSource>(list.Count);
stack.Push(seed);

while (e.MoveNext())
for (var i = list.Count - 2; i >= 0; i--)
{
seed = func(e.Current, seed);
seed = func(list[i], seed);
stack.Push(seed);
}

Expand All @@ -57,31 +58,30 @@ static IEnumerable<TSource> _(IEnumerable<TSource> source, Func<TSource, TSource
}

/// <summary>
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements.
/// The specified seed value is used as the initial accumulator value.
/// This operator is the right-associative version of the
/// <see cref="EnumerableEx.Scan{TSource, TState}(IEnumerable{TSource}, TState, Func{TState, TSource, TState})"/> LINQ operator.
/// Performs a right-associative scan (inclusive prefix) on a sequence of elements. The specified seed value is used
/// as the initial accumulator value. This operator is the right-associative version of the <see
/// cref="EnumerableEx.Scan{TSource, TState}(IEnumerable{TSource}, TState, Func{TState, TSource, TState})"/> LINQ
/// operator.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <typeparam name="TAccumulate">The type of the accumulator value.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="seed">The initial accumulator value.</param>
/// <param name="func">A right-associative accumulator function to be invoked on each element.</param>
/// <returns>The scanned sequence.</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="seed"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/>, <paramref name="seed"/>, or <paramref name="func"/> is <see langword="null"/>.
/// </exception>
/// <example>
/// <code><![CDATA[
/// var result = Enumerable.Range(1, 4).ScanRight("5", (a, b) => string.Format("({0}/{1})", a, b));
/// <code><![CDATA[ var result = Enumerable.Range(1, 4).ScanRight("5", (a, b) => string.Format("({0}/{1})", a, b));
/// ]]></code>
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]</c>.
/// The <c>result</c> variable will contain <c>[ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5"
/// ]</c>.
/// </example>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// Source sequence is consumed greedily when an iteration of the resulting sequence begins.
/// This operator uses deferred execution and streams its results. Source sequence is consumed greedily when an
/// iteration of the resulting sequence begins.
/// </remarks>

public static IEnumerable<TAccumulate> ScanRight<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TSource, TAccumulate, TAccumulate> func)
{
Guard.IsNotNull(source);
Expand All @@ -91,12 +91,13 @@ public static IEnumerable<TAccumulate> ScanRight<TSource, TAccumulate>(this IEnu

static IEnumerable<TAccumulate> _(IEnumerable<TSource> source, TAccumulate seed, Func<TSource, TAccumulate, TAccumulate> func)
{
var stack = new Stack<TAccumulate>();
var list = source is IList<TSource> l ? l : source.ToList();
var stack = new Stack<TAccumulate>(list.Count + 1);
stack.Push(seed);

foreach (var i in source.Reverse())
for (var i = list.Count - 1; i >= 0; i--)
{
seed = func(i, seed);
seed = func(list[i], seed);
stack.Push(seed);
}

Expand Down

0 comments on commit 6298c03

Please sign in to comment.