From 6298c03356fc24d18ad3a7dd0681f8c13eb0b582 Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Sun, 22 Jan 2023 18:13:18 -0600 Subject: [PATCH] Improve performance of `ScanRight` --- Source/SuperLinq.Async/ScanRight.cs | 95 +++++++++++++++-------------- Source/SuperLinq/ScanRight.cs | 69 ++++++++++----------- 2 files changed, 85 insertions(+), 79 deletions(-) diff --git a/Source/SuperLinq.Async/ScanRight.cs b/Source/SuperLinq.Async/ScanRight.cs index e5e509dad..32e8c4cfc 100644 --- a/Source/SuperLinq.Async/ScanRight.cs +++ b/Source/SuperLinq.Async/ScanRight.cs @@ -3,28 +3,29 @@ public static partial class AsyncSuperEnumerable { /// - /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. - /// This operator is the right-associative version of the - /// LINQ operator. + /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. This operator is the + /// right-associative version of the LINQ operator. /// /// Type of elements in source sequence. /// Source sequence. /// - /// 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. /// /// The scanned sequence. - /// is . - /// is . + /// + /// or is . + /// /// - /// i.ToString()).ScanRight((a, b) => $"({a}+{b})"); + /// i.ToString()).ScanRight((a, b) => $"({a}+{b})"); /// ]]> - /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. + /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" + /// ]. /// /// - /// 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. /// public static IAsyncEnumerable ScanRight(this IAsyncEnumerable source, Func func) { @@ -32,28 +33,29 @@ public static IAsyncEnumerable ScanRight(this IAsyncEnumerable } /// - /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. - /// This operator is the right-associative version of the - /// LINQ operator. + /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. This operator is the + /// right-associative version of the LINQ operator. /// /// Type of elements in source sequence. /// Source sequence. /// - /// 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. /// /// The scanned sequence. - /// is . - /// is . + /// + /// , is . + /// /// - /// i.ToString()).ScanRight((a, b) => $"({a}+{b})"); + /// i.ToString()).ScanRight((a, b) => $"({a}+{b})"); /// ]]> - /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. + /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" + /// ]. /// /// - /// 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. /// public static IAsyncEnumerable ScanRight(this IAsyncEnumerable source, Func> func) { @@ -61,28 +63,29 @@ public static IAsyncEnumerable ScanRight(this IAsyncEnumerable } /// - /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. - /// This operator is the right-associative version of the - /// LINQ operator. + /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. This operator is the + /// right-associative version of the LINQ operator. /// /// Type of elements in source sequence. /// Source sequence. /// - /// 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. /// /// The scanned sequence. - /// is . - /// is . + /// + /// or is . + /// /// - /// i.ToString()).ScanRight((a, b) => $"({a}+{b})"); + /// i.ToString()).ScanRight((a, b) => $"({a}+{b})"); /// ]]> - /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. + /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" + /// ]. /// /// - /// 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. /// public static IAsyncEnumerable ScanRight(this IAsyncEnumerable source, Func> func) { @@ -93,18 +96,18 @@ public static IAsyncEnumerable ScanRight(this IAsyncEnumerable static async IAsyncEnumerable _(IAsyncEnumerable source, Func> 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(); + var seed = list[^1]; + var stack = new Stack(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); } @@ -207,12 +210,14 @@ public static IAsyncEnumerable ScanRight(this static async IAsyncEnumerable _(IAsyncEnumerable source, TAccumulate seed, Func> func, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - var stack = new Stack(); + var list = await source.ToListAsync(cancellationToken).ConfigureAwait(false); + + var stack = new Stack(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); } diff --git a/Source/SuperLinq/ScanRight.cs b/Source/SuperLinq/ScanRight.cs index 3c3c7f465..5d3cc3e32 100644 --- a/Source/SuperLinq/ScanRight.cs +++ b/Source/SuperLinq/ScanRight.cs @@ -3,30 +3,31 @@ public static partial class SuperEnumerable { /// - /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. - /// This operator is the right-associative version of the - /// LINQ operator. + /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. This operator is the + /// right-associative version of the LINQ operator. /// /// Type of elements in source sequence. /// Source sequence. /// - /// 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. /// /// The scanned sequence. - /// is . - /// is . + /// + /// or is . + /// /// - /// i.ToString()).ScanRight((a, b) => $"({a}+{b})"); /// ]]> - /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. + /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" + /// ]. /// /// - /// 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. /// - public static IEnumerable ScanRight(this IEnumerable source, Func func) { Guard.IsNotNull(source); @@ -36,18 +37,18 @@ public static IEnumerable ScanRight(this IEnumerable static IEnumerable _(IEnumerable source, Func func) { - using var e = source.Reverse().GetEnumerator(); + var list = source is IList l ? l : source.ToList(); - if (!e.MoveNext()) + if (list.Count == 0) yield break; - var seed = e.Current; - var stack = new Stack(); + var seed = list[^1]; + var stack = new Stack(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); } @@ -57,10 +58,10 @@ static IEnumerable _(IEnumerable source, Func - /// 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 - /// 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 LINQ + /// operator. /// /// The type of the elements of source. /// The type of the accumulator value. @@ -68,20 +69,19 @@ static IEnumerable _(IEnumerable source, FuncThe initial accumulator value. /// A right-associative accumulator function to be invoked on each element. /// The scanned sequence. - /// is . - /// is . - /// is . + /// + /// , , or is . + /// /// - /// string.Format("({0}/{1})", a, b)); + /// string.Format("({0}/{1})", a, b)); /// ]]> - /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. + /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" + /// ]. /// /// - /// 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. /// - public static IEnumerable ScanRight(this IEnumerable source, TAccumulate seed, Func func) { Guard.IsNotNull(source); @@ -91,12 +91,13 @@ public static IEnumerable ScanRight(this IEnu static IEnumerable _(IEnumerable source, TAccumulate seed, Func func) { - var stack = new Stack(); + var list = source is IList l ? l : source.ToList(); + var stack = new Stack(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); }