From 91286ecd913dc9bd4b7269e4dbd3256b7935d781 Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Tue, 5 Jul 2022 16:47:21 -0500 Subject: [PATCH] Correct .Scan() to use matching behavior The scan operator in most languages includes the seed or first element. This commit updates the Scan operator in System.Interactive to match both Rx.NET and other languages. --- .../System/Linq/Operators/Scan.cs | 2 ++ .../System/Linq/Operators/Scan.cs | 16 ++++++++++++---- .../System/Linq/Operators/Scan.cs | 4 ++-- .../System/Linq/Operators/Scan.cs | 9 ++++++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Ix.NET/Source/System.Interactive.Async.Tests/System/Linq/Operators/Scan.cs b/Ix.NET/Source/System.Interactive.Async.Tests/System/Linq/Operators/Scan.cs index b9bf144686..b9c8fc6e1b 100644 --- a/Ix.NET/Source/System.Interactive.Async.Tests/System/Linq/Operators/Scan.cs +++ b/Ix.NET/Source/System.Interactive.Async.Tests/System/Linq/Operators/Scan.cs @@ -28,6 +28,7 @@ public async Task Scan1Async() var xs = new[] { 1, 2, 3 }.ToAsyncEnumerable().Scan(8, (x, y) => x + y); var e = xs.GetAsyncEnumerator(); + await HasNextAsync(e, 8); await HasNextAsync(e, 9); await HasNextAsync(e, 11); await HasNextAsync(e, 14); @@ -40,6 +41,7 @@ public async Task Scan2Async() var xs = new[] { 1, 2, 3 }.ToAsyncEnumerable().Scan((x, y) => x + y); var e = xs.GetAsyncEnumerator(); + await HasNextAsync(e, 1); await HasNextAsync(e, 3); await HasNextAsync(e, 6); await NoNextAsync(e); diff --git a/Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Scan.cs b/Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Scan.cs index fa0aca1c17..c98e5f1785 100644 --- a/Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Scan.cs +++ b/Ix.NET/Source/System.Interactive.Async/System/Linq/Operators/Scan.cs @@ -10,10 +10,6 @@ namespace System.Linq { public static partial class AsyncEnumerableEx { - // NB: Implementations of Scan never yield the first element, unlike the behavior of Aggregate on a sequence with one - // element, which returns the first element (or the seed if given an empty sequence). This is compatible with Rx - // but one could argue whether it was the right default. - /// /// Applies an accumulator function over an async-enumerable sequence and returns each intermediate result. /// For aggregation behavior with no intermediate results, see . @@ -43,6 +39,8 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source, Fu var res = e.Current; + yield return res; + while (await e.MoveNextAsync()) { res = accumulator(res, e.Current); @@ -76,6 +74,8 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source { var res = seed; + yield return res; + await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false)) { res = accumulator(res, item); @@ -114,6 +114,8 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source, Fu var res = e.Current; + yield return res; + while (await e.MoveNextAsync()) { res = await accumulator(res, e.Current).ConfigureAwait(false); @@ -153,6 +155,8 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source, Fu var res = e.Current; + yield return res; + while (await e.MoveNextAsync()) { res = await accumulator(res, e.Current, cancellationToken).ConfigureAwait(false); @@ -187,6 +191,8 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source { var res = seed; + yield return res; + await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false)) { res = await accumulator(res, item).ConfigureAwait(false); @@ -221,6 +227,8 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source { var res = seed; + yield return res; + await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false)) { res = await accumulator(res, item, cancellationToken).ConfigureAwait(false); diff --git a/Ix.NET/Source/System.Interactive.Tests/System/Linq/Operators/Scan.cs b/Ix.NET/Source/System.Interactive.Tests/System/Linq/Operators/Scan.cs index 282f0c279f..ac236155fe 100644 --- a/Ix.NET/Source/System.Interactive.Tests/System/Linq/Operators/Scan.cs +++ b/Ix.NET/Source/System.Interactive.Tests/System/Linq/Operators/Scan.cs @@ -23,14 +23,14 @@ public void Scan_Arguments() public void Scan1() { var res = Enumerable.Range(0, 5).Scan((n, x) => n + x).ToList(); - Assert.True(Enumerable.SequenceEqual(res, new[] { 1, 3, 6, 10 })); + Assert.True(Enumerable.SequenceEqual(res, new[] { 0, 1, 3, 6, 10 })); } [Fact] public void Scan2() { var res = Enumerable.Range(0, 5).Scan(10, (n, x) => n - x).ToList(); - Assert.True(Enumerable.SequenceEqual(res, new[] { 10, 9, 7, 4, 0 })); + Assert.True(Enumerable.SequenceEqual(res, new[] { 10, 10, 9, 7, 4, 0 })); } } } diff --git a/Ix.NET/Source/System.Interactive/System/Linq/Operators/Scan.cs b/Ix.NET/Source/System.Interactive/System/Linq/Operators/Scan.cs index cbfdc3c462..9405974a15 100644 --- a/Ix.NET/Source/System.Interactive/System/Linq/Operators/Scan.cs +++ b/Ix.NET/Source/System.Interactive/System/Linq/Operators/Scan.cs @@ -54,6 +54,8 @@ private static IEnumerable ScanCore(IEnumerab { var acc = seed; + yield return acc; + foreach (var item in source) { acc = accumulator(acc, item); @@ -73,10 +75,11 @@ private static IEnumerable ScanCore(IEnumerable sourc { hasSeed = true; acc = item; - continue; } - - acc = accumulator(acc, item); + else + { + acc = accumulator(acc, item); + } yield return acc; }