From c412e255919eb438161bc96329cb2f92f14ef2e6 Mon Sep 17 00:00:00 2001 From: akarnokd Date: Sat, 3 Nov 2018 00:30:07 +0100 Subject: [PATCH] + DistinctUntilChanged --- async-enumerable-dotnet-test/DistinctTest.cs | 2 +- .../DistinctUntilChangedTest.cs | 69 +++++++++++++++ async-enumerable-dotnet/AsyncEnumerable.cs | 63 +++++++++++++- .../impl/DistinctUntilChanged.cs | 87 +++++++++++++++++++ 4 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 async-enumerable-dotnet-test/DistinctUntilChangedTest.cs create mode 100644 async-enumerable-dotnet/impl/DistinctUntilChanged.cs diff --git a/async-enumerable-dotnet-test/DistinctTest.cs b/async-enumerable-dotnet-test/DistinctTest.cs index 3d1c237..312d349 100644 --- a/async-enumerable-dotnet-test/DistinctTest.cs +++ b/async-enumerable-dotnet-test/DistinctTest.cs @@ -62,7 +62,7 @@ await AsyncEnumerable.Range(1, 5) public async void Custom_Set() { await AsyncEnumerable.Range(1, 5) - .Distinct(v => (v % 3), () => new HashSet()) + .Distinct(v => v % 3, () => new HashSet()) .AssertResult(1, 2, 3); } } diff --git a/async-enumerable-dotnet-test/DistinctUntilChangedTest.cs b/async-enumerable-dotnet-test/DistinctUntilChangedTest.cs new file mode 100644 index 0000000..34f1701 --- /dev/null +++ b/async-enumerable-dotnet-test/DistinctUntilChangedTest.cs @@ -0,0 +1,69 @@ +// Copyright (c) David Karnok & Contributors. +// Licensed under the Apache 2.0 License. +// See LICENSE file in the project root for full license information. + +using Xunit; +using async_enumerable_dotnet; +using System.Collections.Generic; + +namespace async_enumerable_dotnet_test +{ + public class DistinctUntilChangedTest + { + [Fact] + public async void Empty() + { + await AsyncEnumerable.Empty() + .DistinctUntilChanged() + .AssertResult(); + } + + [Fact] + public async void Just() + { + await AsyncEnumerable.Just(1) + .DistinctUntilChanged() + .AssertResult(1); + } + + [Fact] + public async void Range() + { + await AsyncEnumerable.Range(1, 5) + .DistinctUntilChanged() + .AssertResult(1, 2, 3, 4, 5); + } + + [Fact] + public async void Redundant() + { + await AsyncEnumerable.FromArray(1, 2, 3, 3, 2, 1, 1, 4, 5, 4, 4) + .DistinctUntilChanged() + .AssertResult(1, 2, 3, 2, 1, 4, 5, 4); + } + + [Fact] + public async void Redundant_Comparer() + { + await AsyncEnumerable.FromArray(1, 2, 3, 3, 2, 1, 1, 4, 5, 4, 4) + .DistinctUntilChanged(EqualityComparer.Default) + .AssertResult(1, 2, 3, 2, 1, 4, 5, 4); + } + + [Fact] + public async void KeySelector() + { + await AsyncEnumerable.Range(1, 10) + .DistinctUntilChanged(k => k / 3) + .AssertResult(1, 3, 6, 9); + } + + [Fact] + public async void KeySelector_KeyComparer() + { + await AsyncEnumerable.Range(1, 10) + .DistinctUntilChanged(k => k / 3, EqualityComparer.Default) + .AssertResult(1, 3, 6, 9); + } + } +} diff --git a/async-enumerable-dotnet/AsyncEnumerable.cs b/async-enumerable-dotnet/AsyncEnumerable.cs index 5922382..cc777cc 100644 --- a/async-enumerable-dotnet/AsyncEnumerable.cs +++ b/async-enumerable-dotnet/AsyncEnumerable.cs @@ -1678,7 +1678,7 @@ public static IAsyncEnumerable Distinct(this IAsyncEnumerable< /// /// Checks if the source items have been seen before by checking - /// a key extracted from such items agains a set of keys and + /// a key extracted from such items against a set of keys and /// drops such items, ensuring that only distinct source items pass /// through. /// @@ -1696,5 +1696,66 @@ public static IAsyncEnumerable Distinct(this IAsyncEnumerable< RequireNonNull(setSupplier, nameof(setSupplier)); return new Distinct(source, keySelector, setSupplier); } + + /// + /// Checks if the current item is distinct from the previous item, + /// and if so it is no it is relayed. + /// + /// The element type of the source and result sequences. + /// The source sequence to filter for distinct subsequent items. + /// The new IAsyncEnumerable sequence. + public static IAsyncEnumerable DistinctUntilChanged(this IAsyncEnumerable source) + { + return DistinctUntilChanged(source, v => v, EqualityComparer.Default); + } + + /// + /// Checks if the current item is distinct from the previous item, + /// and if so it is no it is relayed, + /// based on comparing the items via a custom equality comparer. + /// + /// The element type of the source and result sequences. + /// The source sequence to filter for distinct subsequent items. + /// The comparer for comparing the source items. + /// The new IAsyncEnumerable sequence. + public static IAsyncEnumerable DistinctUntilChanged(this IAsyncEnumerable source, IEqualityComparer comparer) + { + return DistinctUntilChanged(source, v => v, comparer); + } + + /// + /// Checks if the current item is distinct from the previous item, + /// and if so it is no it is relayed, + /// based on comparing keys extracted via a function. + /// + /// The element type of the source and result sequences. + /// The type of the keys extracted for comparison + /// The source sequence to filter for distinct subsequent items. + /// The function receiving the current source item and should return a key value for comparison. + /// The new IAsyncEnumerable sequence. + public static IAsyncEnumerable DistinctUntilChanged(this IAsyncEnumerable source, Func keySelector) + { + return DistinctUntilChanged(source, keySelector, EqualityComparer.Default); + } + + /// + /// Checks if the current item is distinct from the previous item, + /// and if so it is no it is relayed, + /// based on comparing keys extracted via a function and using + /// a custom equality comparer. + /// + /// The element type of the source and result sequences. + /// The type of the keys extracted for comparison + /// The source sequence to filter for distinct subsequent items. + /// The function receiving the current source item and should return a key value for comparison. + /// The comparer for comparing the key values extracted via . + /// The new IAsyncEnumerable sequence. + public static IAsyncEnumerable DistinctUntilChanged(this IAsyncEnumerable source, Func keySelector, IEqualityComparer keyComparer) + { + RequireNonNull(source, nameof(source)); + RequireNonNull(keySelector, nameof(keySelector)); + RequireNonNull(keyComparer, nameof(keyComparer)); + return new DistinctUntilChanged(source, keySelector, keyComparer); + } } } diff --git a/async-enumerable-dotnet/impl/DistinctUntilChanged.cs b/async-enumerable-dotnet/impl/DistinctUntilChanged.cs new file mode 100644 index 0000000..c7f523a --- /dev/null +++ b/async-enumerable-dotnet/impl/DistinctUntilChanged.cs @@ -0,0 +1,87 @@ +// Copyright (c) David Karnok & Contributors. +// Licensed under the Apache 2.0 License. +// See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace async_enumerable_dotnet.impl +{ + internal sealed class DistinctUntilChanged : IAsyncEnumerable + { + private readonly IAsyncEnumerable _source; + + private readonly Func _keySelector; + + private readonly IEqualityComparer _keyComparer; + + public DistinctUntilChanged(IAsyncEnumerable source, Func keySelector, IEqualityComparer keyComparer) + { + _source = source; + _keySelector = keySelector; + _keyComparer = keyComparer; + } + + public IAsyncEnumerator GetAsyncEnumerator() + { + return new DistinctUntilChangedEnumerator(_source.GetAsyncEnumerator(), _keySelector, _keyComparer); + } + + private sealed class DistinctUntilChangedEnumerator : IAsyncEnumerator + { + private readonly IAsyncEnumerator _source; + + private readonly Func _keySelector; + + private readonly IEqualityComparer _keyComparer; + + private TKey _prevKey; + + public TSource Current => _source.Current; + + private bool _once; + + public DistinctUntilChangedEnumerator(IAsyncEnumerator source, Func keySelector, IEqualityComparer keyComparer) + { + _source = source; + _keySelector = keySelector; + _keyComparer = keyComparer; + } + + public ValueTask DisposeAsync() + { + _prevKey = default; + return _source.DisposeAsync(); + } + + public async ValueTask MoveNextAsync() + { + for (; ;) + { + if (await _source.MoveNextAsync()) + { + var key = _keySelector(_source.Current); + if (!_once) + { + _once = true; + _prevKey = key; + return true; + } + if (!_keyComparer.Equals(_prevKey, key)) + { + _prevKey = key; + return true; + } + + _prevKey = key; + } + else + { + return false; + } + } + } + } + } +}