From a4ff6ab12ae85f99e728173286b3bc90d9be0818 Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Fri, 24 Nov 2023 22:20:06 -0600 Subject: [PATCH] Add `Duplicates` Operator (#592) * Add sync `Duplicates` operator * Add async `Duplicates` operator * Simplify `HasDuplicates` operator * Simplify `HasDuplicates` tests * Update front-page readme * Add linqpad example and link example --- .../SuperLinq.SuperEnumerable.Duplicates.md | 6 + .../SuperLinq/Duplicates/Duplicates.linq | 17 ++ README.md | 1 + Source/SuperLinq.Async/Duplicates.cs | 55 ++++ Source/SuperLinq.Async/HasDuplicates.cs | 21 +- .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/net7.0/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 1 + .../netcoreapp3.1/PublicAPI.Unshipped.txt | 1 + Source/SuperLinq/Duplicates.cs | 52 ++++ Source/SuperLinq/HasDuplicates.cs | 17 +- .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/net7.0/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 1 + .../netcoreapp3.1/PublicAPI.Unshipped.txt | 5 +- Tests/SuperLinq.Async.Test/DuplicatesTest.cs | 60 +++++ .../SuperLinq.Async.Test/HasDuplicatesTest.cs | 231 +++-------------- Tests/SuperLinq.Test/DuplicatesTest.cs | 60 +++++ Tests/SuperLinq.Test/HasDuplicatesTest.cs | 244 ++++-------------- 19 files changed, 365 insertions(+), 411 deletions(-) create mode 100644 Docs/SuperLinq.Docs/apidoc/SuperLinq.SuperEnumerable.Duplicates.md create mode 100644 Docs/SuperLinq.Docs/apidoc/SuperLinq/Duplicates/Duplicates.linq create mode 100644 Source/SuperLinq.Async/Duplicates.cs create mode 100644 Source/SuperLinq/Duplicates.cs create mode 100644 Tests/SuperLinq.Async.Test/DuplicatesTest.cs create mode 100644 Tests/SuperLinq.Test/DuplicatesTest.cs diff --git a/Docs/SuperLinq.Docs/apidoc/SuperLinq.SuperEnumerable.Duplicates.md b/Docs/SuperLinq.Docs/apidoc/SuperLinq.SuperEnumerable.Duplicates.md new file mode 100644 index 000000000..39ccdce7a --- /dev/null +++ b/Docs/SuperLinq.Docs/apidoc/SuperLinq.SuperEnumerable.Duplicates.md @@ -0,0 +1,6 @@ +--- +uid: SuperLinq.SuperEnumerable.Duplicates``1(System.Collections.Generic.IEnumerable{``0},System.Collections.Generic.IEqualityComparer{``0}) +example: [*content] +--- +The following code example demonstrates how to get the duplicated elements of a sequence using `Duplicates`. +[!code-csharp[](SuperLinq/Duplicates/Duplicates.linq#L6-)] diff --git a/Docs/SuperLinq.Docs/apidoc/SuperLinq/Duplicates/Duplicates.linq b/Docs/SuperLinq.Docs/apidoc/SuperLinq/Duplicates/Duplicates.linq new file mode 100644 index 000000000..704fb20af --- /dev/null +++ b/Docs/SuperLinq.Docs/apidoc/SuperLinq/Duplicates/Duplicates.linq @@ -0,0 +1,17 @@ + + SuperLinq + SuperLinq + + +var sequence = new[] { "foo", "bar", "baz", "foo", }; + +// determine if a sequence has duplicate items +var result = sequence.Duplicates(); + +Console.WriteLine( + "[" + + string.Join(", ", result) + + "]"); + +// This code produces the following output: +// [foo] diff --git a/README.md b/README.md index db94099e9..0d19b1c29 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ equivalent elements within the same or separate collections (or sets). | Method Name | Description | Sync doc | Async doc | | ----------- | --- | --- | --- | | DistinctBy | Removes duplicate values from a collection. | [link](https://viceroypenguin.github.io/SuperLinq/api/SuperLinq.SuperEnumerable.DistinctBy.html) | [link](https://viceroypenguin.github.io/SuperLinq/api/SuperLinq.Async.AsyncSuperEnumerable.DistinctBy.html) | +| Duplicates | Returns the sequence of elements that are in the source sequence more than once. | [link](https://viceroypenguin.github.io/SuperLinq/api/SuperLinq.SuperEnumerable.Duplicates.html) | [link](https://viceroypenguin.github.io/SuperLinq/api/SuperLinq.Async.AsyncSuperEnumerable.Duplicates.html) | | ExceptBy | Returns the set difference, which means the elements of one collection that do not appear in a second collection. | [link](https://viceroypenguin.github.io/SuperLinq/api/SuperLinq.SuperEnumerable.ExceptBy.html) | [link](https://viceroypenguin.github.io/SuperLinq/api/SuperLinq.Async.AsyncSuperEnumerable.ExceptBy.html) | diff --git a/Source/SuperLinq.Async/Duplicates.cs b/Source/SuperLinq.Async/Duplicates.cs new file mode 100644 index 000000000..ae22f88dd --- /dev/null +++ b/Source/SuperLinq.Async/Duplicates.cs @@ -0,0 +1,55 @@ +namespace SuperLinq.Async; + +public static partial class AsyncSuperEnumerable +{ + /// + /// Returns all duplicate elements of the given source. + /// + /// + /// The type of the elements in the source sequence. + /// + /// + /// The source sequence. + /// + /// + /// The equality comparer to use to determine whether one equals another. If , the default equality comparer for is used. + /// + /// + /// All elements that are duplicated. + /// + /// + /// is . + /// + /// + /// This operator uses deferred execution and streams its results. + /// + public static IAsyncEnumerable Duplicates(this IAsyncEnumerable source, IEqualityComparer? comparer = null) + { + ArgumentNullException.ThrowIfNull(source); + + comparer ??= EqualityComparer.Default; + + return Core(source, comparer); + + static async IAsyncEnumerable Core( + IAsyncEnumerable source, + IEqualityComparer comparer, + [EnumeratorCancellation] CancellationToken token = default) + { + var counts = new Collections.NullKeyDictionary(comparer); + await foreach (var element in source.WithCancellation(token).ConfigureAwait(false)) + { + if (!counts.TryGetValue(element, out var count)) + { + counts[element] = 1; + } + else if (count == 1) + { + yield return element; + counts[element] = 2; + } + } + } + } +} diff --git a/Source/SuperLinq.Async/HasDuplicates.cs b/Source/SuperLinq.Async/HasDuplicates.cs index 23033cb57..23ada12d8 100644 --- a/Source/SuperLinq.Async/HasDuplicates.cs +++ b/Source/SuperLinq.Async/HasDuplicates.cs @@ -62,22 +62,9 @@ public static ValueTask HasDuplicates(this IAsyncEnumerable ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(keySelector); - return Core(source, keySelector, comparer); - - static async ValueTask Core(IAsyncEnumerable source, Func keySelector, IEqualityComparer? comparer) - { - var enumeratedElements = new HashSet(comparer); - - await foreach (var element in source.ConfigureAwait(false)) - { - if (!enumeratedElements.Add(keySelector(element))) - { - return true; - } - } - - return false; - } + return source + .Select(keySelector) + .Duplicates(comparer) + .AnyAsync(); } } - diff --git a/Source/SuperLinq.Async/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/Source/SuperLinq.Async/PublicAPI/net6.0/PublicAPI.Unshipped.txt index bdc6e2412..a92b0e838 100644 --- a/Source/SuperLinq.Async/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/Source/SuperLinq.Async/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ #nullable enable static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable>! static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable>! +static SuperLinq.Async.AsyncSuperEnumerable.Duplicates(this System.Collections.Generic.IAsyncEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable! static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IEnumerable!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<(System.Collections.Generic.IEnumerable! True, System.Collections.Generic.IEnumerable! False)> *REMOVED*static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IAsyncEnumerable!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask diff --git a/Source/SuperLinq.Async/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/Source/SuperLinq.Async/PublicAPI/net7.0/PublicAPI.Unshipped.txt index bdc6e2412..a92b0e838 100644 --- a/Source/SuperLinq.Async/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ b/Source/SuperLinq.Async/PublicAPI/net7.0/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ #nullable enable static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable>! static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable>! +static SuperLinq.Async.AsyncSuperEnumerable.Duplicates(this System.Collections.Generic.IAsyncEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable! static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IEnumerable!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<(System.Collections.Generic.IEnumerable! True, System.Collections.Generic.IEnumerable! False)> *REMOVED*static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IAsyncEnumerable!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask diff --git a/Source/SuperLinq.Async/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/Source/SuperLinq.Async/PublicAPI/net8.0/PublicAPI.Unshipped.txt index bdc6e2412..a92b0e838 100644 --- a/Source/SuperLinq.Async/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/Source/SuperLinq.Async/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ #nullable enable static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable>! static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable>! +static SuperLinq.Async.AsyncSuperEnumerable.Duplicates(this System.Collections.Generic.IAsyncEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable! static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IEnumerable!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<(System.Collections.Generic.IEnumerable! True, System.Collections.Generic.IEnumerable! False)> *REMOVED*static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IAsyncEnumerable!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask diff --git a/Source/SuperLinq.Async/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt b/Source/SuperLinq.Async/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt index bdc6e2412..a92b0e838 100644 --- a/Source/SuperLinq.Async/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt +++ b/Source/SuperLinq.Async/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ #nullable enable static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable>! static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable>! +static SuperLinq.Async.AsyncSuperEnumerable.Duplicates(this System.Collections.Generic.IAsyncEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IAsyncEnumerable! static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IEnumerable!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<(System.Collections.Generic.IEnumerable! True, System.Collections.Generic.IEnumerable! False)> *REMOVED*static SuperLinq.Async.AsyncSuperEnumerable.Partition(this System.Collections.Generic.IAsyncEnumerable! source, System.Func! predicate, System.Func!, System.Collections.Generic.IAsyncEnumerable!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask diff --git a/Source/SuperLinq/Duplicates.cs b/Source/SuperLinq/Duplicates.cs new file mode 100644 index 000000000..d458a7373 --- /dev/null +++ b/Source/SuperLinq/Duplicates.cs @@ -0,0 +1,52 @@ +namespace SuperLinq; + +public static partial class SuperEnumerable +{ + /// + /// Returns all duplicate elements of the given source. + /// + /// + /// The type of the elements in the source sequence. + /// + /// + /// The source sequence. + /// + /// + /// The equality comparer to use to determine whether one equals another. If , the default equality comparer for is used. + /// + /// + /// All elements that are duplicated. + /// + /// + /// is . + /// + /// + /// This operator uses deferred execution and streams its results. + /// + public static IEnumerable Duplicates(this IEnumerable source, IEqualityComparer? comparer = null) + { + ArgumentNullException.ThrowIfNull(source); + + comparer ??= EqualityComparer.Default; + + return Core(source, comparer); + + static IEnumerable Core(IEnumerable source, IEqualityComparer comparer) + { + var counts = new Collections.NullKeyDictionary(comparer); + foreach (var element in source) + { + if (!counts.TryGetValue(element, out var count)) + { + counts[element] = 1; + } + else if (count == 1) + { + yield return element; + counts[element] = 2; + } + } + } + } +} diff --git a/Source/SuperLinq/HasDuplicates.cs b/Source/SuperLinq/HasDuplicates.cs index 385d7bf41..71bc9b789 100644 --- a/Source/SuperLinq/HasDuplicates.cs +++ b/Source/SuperLinq/HasDuplicates.cs @@ -130,18 +130,9 @@ public static bool HasDuplicates( ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(keySelector); - var enumeratedElements = source.TryGetCollectionCount() is { } collectionCount - ? new HashSet(collectionCount, comparer) - : new HashSet(comparer); - - foreach (var element in source) - { - if (!enumeratedElements.Add(keySelector(element))) - { - return true; - } - } - - return false; + return source + .Select(keySelector) + .Duplicates(comparer) + .Any(); } } diff --git a/Source/SuperLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/Source/SuperLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt index 436bf3c76..da5efbac1 100644 --- a/Source/SuperLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/Source/SuperLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -3,4 +3,5 @@ *REMOVED*static SuperLinq.SuperEnumerable.Share(this System.Collections.Generic.IEnumerable! source, System.Func!, System.Collections.Generic.IEnumerable!>! selector) -> System.Collections.Generic.IEnumerable! static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! +static SuperLinq.SuperEnumerable.Duplicates(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable! static SuperLinq.SuperEnumerable.Take(System.Collections.Generic.IEnumerable! source, System.Range range) -> System.Collections.Generic.IEnumerable! diff --git a/Source/SuperLinq/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/Source/SuperLinq/PublicAPI/net7.0/PublicAPI.Unshipped.txt index 436bf3c76..da5efbac1 100644 --- a/Source/SuperLinq/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ b/Source/SuperLinq/PublicAPI/net7.0/PublicAPI.Unshipped.txt @@ -3,4 +3,5 @@ *REMOVED*static SuperLinq.SuperEnumerable.Share(this System.Collections.Generic.IEnumerable! source, System.Func!, System.Collections.Generic.IEnumerable!>! selector) -> System.Collections.Generic.IEnumerable! static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! +static SuperLinq.SuperEnumerable.Duplicates(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable! static SuperLinq.SuperEnumerable.Take(System.Collections.Generic.IEnumerable! source, System.Range range) -> System.Collections.Generic.IEnumerable! diff --git a/Source/SuperLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/Source/SuperLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 436bf3c76..da5efbac1 100644 --- a/Source/SuperLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/Source/SuperLinq/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -3,4 +3,5 @@ *REMOVED*static SuperLinq.SuperEnumerable.Share(this System.Collections.Generic.IEnumerable! source, System.Func!, System.Collections.Generic.IEnumerable!>! selector) -> System.Collections.Generic.IEnumerable! static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! +static SuperLinq.SuperEnumerable.Duplicates(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable! static SuperLinq.SuperEnumerable.Take(System.Collections.Generic.IEnumerable! source, System.Range range) -> System.Collections.Generic.IEnumerable! diff --git a/Source/SuperLinq/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt b/Source/SuperLinq/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt index e72585ec0..b25723e77 100644 --- a/Source/SuperLinq/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt +++ b/Source/SuperLinq/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt @@ -1,5 +1,6 @@ #nullable enable -static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! -static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! *REMOVED*static SuperLinq.SuperEnumerable.Publish(this System.Collections.Generic.IEnumerable! source, System.Func!, System.Collections.Generic.IEnumerable!>! selector) -> System.Collections.Generic.IEnumerable! *REMOVED*static SuperLinq.SuperEnumerable.Share(this System.Collections.Generic.IEnumerable! source, System.Func!, System.Collections.Generic.IEnumerable!>! selector) -> System.Collections.Generic.IEnumerable! +static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, System.Func! seedSelector, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! +static SuperLinq.SuperEnumerable.AggregateBy(this System.Collections.Generic.IEnumerable! source, System.Func! keySelector, TAccumulate seed, System.Func! func, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable>! +static SuperLinq.SuperEnumerable.Duplicates(this System.Collections.Generic.IEnumerable! source, System.Collections.Generic.IEqualityComparer? comparer = null) -> System.Collections.Generic.IEnumerable! diff --git a/Tests/SuperLinq.Async.Test/DuplicatesTest.cs b/Tests/SuperLinq.Async.Test/DuplicatesTest.cs new file mode 100644 index 000000000..c1b9d2f02 --- /dev/null +++ b/Tests/SuperLinq.Async.Test/DuplicatesTest.cs @@ -0,0 +1,60 @@ +namespace Test.Async; + +public class DuplicatesTest +{ + [Fact] + public void DuplicatesIsLazy() + { + _ = new AsyncBreakingSequence().Duplicates(); + } + + [Theory] + [InlineData(new int[] { 1, 2, 3, }, new int[] { })] + [InlineData(new int[] { 1, 2, 1, 3, 1, 2, 1, }, new int[] { 1, 2, })] + [InlineData(new int[] { 3, 3, 2, 2, 1, 1, }, new int[] { 3, 2, 1, })] + public async Task DuplicatesBehavior(IEnumerable source, IEnumerable expected) + { + await using var ts = source.AsTestingSequence(); + + var result = ts.Duplicates(); + await result.AssertSequenceEqual(expected); + } + + public static IEnumerable GetStringParameters() + { + yield return new object[] + { + new string[] { "foo", "bar", "qux" }, + StringComparer.Ordinal, + Array.Empty(), + }; + yield return new object[] + { + new string[] { "foo", "FOO", "bar", "qux" }, + StringComparer.Ordinal, + Array.Empty(), + }; + yield return new object[] + { + new string[] { "foo", "FOO", "bar", "qux" }, + StringComparer.OrdinalIgnoreCase, + new string[] { "FOO", }, + }; + yield return new object[] + { + new string[] { "Bar", "foo", "FOO", "bar", "qux" }, + StringComparer.OrdinalIgnoreCase, + new string[] { "FOO", "bar", }, + }; + } + + [Theory] + [MemberData(nameof(GetStringParameters))] + public async Task DuplicatesComparerBehavior(IEnumerable source, StringComparer comparer, IEnumerable expected) + { + await using var ts = source.AsTestingSequence(); + + var result = ts.Duplicates(comparer); + await result.AssertSequenceEqual(expected); + } +} diff --git a/Tests/SuperLinq.Async.Test/HasDuplicatesTest.cs b/Tests/SuperLinq.Async.Test/HasDuplicatesTest.cs index be1c41a67..f84e93407 100644 --- a/Tests/SuperLinq.Async.Test/HasDuplicatesTest.cs +++ b/Tests/SuperLinq.Async.Test/HasDuplicatesTest.cs @@ -3,212 +3,61 @@ public class HasDuplicatesTest { [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_Without_Duplicates_Then_False_Is_Returned() + public async Task DuplicatesDoesNotEnumerateUnnecessarily() { - await using var asyncSequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); + using var ts = AsyncSeq(1, 2, 3).Concat(AsyncSeqExceptionAt(2)).AsTestingSequence(); - var hasDuplicates = await asyncSequence.HasDuplicates(); - - Assert.False(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_Projection_Without_Duplicates_Then_False_Is_Returned() - { - await using var asyncSequence = TestingSequence.Of( - new DummyClass("FirstElement"), - new DummyClass("SecondElement"), - new DummyClass("ThirdElement")); - - var hasDuplicates = await asyncSequence.HasDuplicates(x => x.ComparableString); - - Assert.False(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_With_Duplicates_Then_True_Is_Returned() - { - await using var asyncSequence = TestingSequence.Of( - "FirstElement", - "DUPLICATED_STRING", - "DUPLICATED_STRING", - "ThirdElement"); - - var hasDuplicates = await asyncSequence.HasDuplicates(); - - Assert.True(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_Projection_With_Duplicates_Then_True_Is_Returned() - { - await using var asyncSequence = TestingSequence.Of( - new DummyClass("FirstElement"), - new DummyClass("DUPLICATED_STRING"), - new DummyClass("DUPLICATED_STRING"), - new DummyClass("ThirdElement")); - - var hasDuplicates = await asyncSequence.HasDuplicates(x => x.ComparableString); - - Assert.True(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_Projection_With_Duplicates_Then_It_Does_Not_Iterate_Unnecessary_On_Elements() - { - await using var asyncSequence = TestingSequence.OfWithFailure( - new DummyClass("FirstElement"), - new DummyClass("DUPLICATED_STRING"), - new DummyClass("DUPLICATED_STRING")); - - var hasDuplicates = await asyncSequence.HasDuplicates(x => x.ComparableString); - - Assert.True(hasDuplicates); - } - - [Fact] - public async Task When_Asking_Duplicates_Then_It_Is_Executed_Right_Away() - { - _ = await Assert.ThrowsAsync(async () => - _ = await new AsyncBreakingSequence().HasDuplicates()); - } - - [Fact] - public async Task When_Asking_Duplicates_On_Sequence_Projection_Then_It_Is_Executed_Right_Away() - { - _ = await Assert.ThrowsAsync(async () => - _ = await new AsyncBreakingSequence().HasDuplicates(x => x.ComparableString)); + var result = await ts.HasDuplicates(); + Assert.True(result); } - [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_With_Custom_Always_True_Comparer_Then_True_Is_Returned() + [Theory] + [InlineData(new int[] { 1, 2, 3, }, false)] + [InlineData(new int[] { 1, 2, 1, 3, 1, 2, 1, }, true)] + [InlineData(new int[] { 3, 3, 2, 2, 1, 1, }, true)] + public async Task DuplicatesBehavior(IEnumerable source, bool expected) { - await using var asyncSequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = await asyncSequence.HasDuplicates(new DummyStringAlwaysTrueComparer()); + await using var ts = source.AsTestingSequence(); - Assert.True(hasDuplicates); + var result = await ts.HasDuplicates(); + Assert.Equal(expected, result); } - [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_Projection_With_Custom_Always_True_Comparer_Then_True_Is_Returned() + public static IEnumerable GetStringParameters() { - await using var asyncSequence = TestingSequence.Of( - new DummyClass("FirstElement"), - new DummyClass("SecondElement"), - new DummyClass("ThirdElement")); - - var hasDuplicates = - await asyncSequence.HasDuplicates(x => x.ComparableString, new DummyStringAlwaysTrueComparer()); - - Assert.True(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_None_Duplicates_Sequence_With_Custom_Always_True_Comparer_Then_True_Is_Returned() - { - await using var asyncSequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = await asyncSequence.HasDuplicates(new DummyStringAlwaysTrueComparer()); - - Assert.True(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_With_Null_Comparer_Then_Default_Comparer_Is_Used_And_False_Is_Returned() - { - await using var asyncSequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = await asyncSequence.HasDuplicates(null); - - Assert.False(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_Sequence_With_Custom_Always_False_Comparer_Then_False_Is_Returned() - { - await using var asyncSequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = await asyncSequence.HasDuplicates(new DummyStringAlwaysFalseComparer()); - - Assert.False(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_Multiple_Duplicates_Sequence_With_Custom_Always_False_Comparer_Then_False_Is_Returned() - { - await using var asyncSequence = TestingSequence.Of( - "DUPLICATED_STRING", - "DUPLICATED_STRING", - "DUPLICATED_STRING"); - - var hasDuplicates = await asyncSequence.HasDuplicates(new DummyStringAlwaysFalseComparer()); - - Assert.False(hasDuplicates); - } - - [Fact] - public async Task When_Asking_For_Duplicates_On_Multiple_Duplicates_Sequence_Projection_With_Custom_Always_False_Comparer_Then_False_Is_Returned() - { - await using var asyncSequence = TestingSequence.Of( - new DummyClass("DUPLICATED_STRING"), - new DummyClass("DUPLICATED_STRING"), - new DummyClass("DUPLICATED_STRING")); - - var hasDuplicates = - await asyncSequence.HasDuplicates(x => x.ComparableString, new DummyStringAlwaysFalseComparer()); - - Assert.False(hasDuplicates); - } - - private sealed class DummyClass - { - public DummyClass(string comparableString) + yield return new object[] { - ComparableString = comparableString; - } - - public string ComparableString { get; } - } - - private sealed class DummyStringAlwaysTrueComparer : IEqualityComparer - { - public bool Equals(string? x, string? y) + new string[] { "foo", "bar", "qux" }, + StringComparer.Ordinal, + false, + }; + yield return new object[] { - return true; - } - - public int GetHashCode(string obj) + new string[] { "foo", "FOO", "bar", "qux" }, + StringComparer.Ordinal, + false, + }; + yield return new object[] + { + new string[] { "foo", "FOO", "bar", "qux" }, + StringComparer.OrdinalIgnoreCase, + true, + }; + yield return new object[] { - return 0; - } + new string[] { "Bar", "foo", "FOO", "bar", "qux" }, + StringComparer.OrdinalIgnoreCase, + true, + }; } - private sealed class DummyStringAlwaysFalseComparer : IEqualityComparer + [Theory] + [MemberData(nameof(GetStringParameters))] + public async Task DuplicatesComparerBehavior(IEnumerable source, StringComparer comparer, bool expected) { - public bool Equals(string? x, string? y) - { - return false; - } + await using var ts = source.AsTestingSequence(); - public int GetHashCode(string obj) - { - return 0; - } + var result = await ts.HasDuplicates(comparer); + Assert.Equal(expected, result); } } diff --git a/Tests/SuperLinq.Test/DuplicatesTest.cs b/Tests/SuperLinq.Test/DuplicatesTest.cs new file mode 100644 index 000000000..cfadab8ab --- /dev/null +++ b/Tests/SuperLinq.Test/DuplicatesTest.cs @@ -0,0 +1,60 @@ +namespace Test; + +public class DuplicatesTest +{ + [Fact] + public void DuplicatesIsLazy() + { + _ = new BreakingSequence().Duplicates(); + } + + [Theory] + [InlineData(new int[] { 1, 2, 3, }, new int[] { })] + [InlineData(new int[] { 1, 2, 1, 3, 1, 2, 1, }, new int[] { 1, 2, })] + [InlineData(new int[] { 3, 3, 2, 2, 1, 1, }, new int[] { 3, 2, 1, })] + public void DuplicatesBehavior(IEnumerable source, IEnumerable expected) + { + using var ts = source.AsTestingSequence(); + + var result = ts.Duplicates(); + result.AssertSequenceEqual(expected); + } + + public static IEnumerable GetStringParameters() + { + yield return new object[] + { + new string[] { "foo", "bar", "qux" }, + StringComparer.Ordinal, + Array.Empty(), + }; + yield return new object[] + { + new string[] { "foo", "FOO", "bar", "qux" }, + StringComparer.Ordinal, + Array.Empty(), + }; + yield return new object[] + { + new string[] { "foo", "FOO", "bar", "qux" }, + StringComparer.OrdinalIgnoreCase, + new string[] { "FOO", }, + }; + yield return new object[] + { + new string[] { "Bar", "foo", "FOO", "bar", "qux" }, + StringComparer.OrdinalIgnoreCase, + new string[] { "FOO", "bar", }, + }; + } + + [Theory] + [MemberData(nameof(GetStringParameters))] + public void DuplicatesComparerBehavior(IEnumerable source, StringComparer comparer, IEnumerable expected) + { + using var ts = source.AsTestingSequence(); + + var result = ts.Duplicates(comparer); + result.AssertSequenceEqual(expected); + } +} diff --git a/Tests/SuperLinq.Test/HasDuplicatesTest.cs b/Tests/SuperLinq.Test/HasDuplicatesTest.cs index 03b3740cc..10ee1d5cf 100644 --- a/Tests/SuperLinq.Test/HasDuplicatesTest.cs +++ b/Tests/SuperLinq.Test/HasDuplicatesTest.cs @@ -3,193 +3,61 @@ public class HasDuplicatesTest { [Fact] - public void When_Asking_For_Duplicates_On_Sequence_Without_Duplicates_Then_False_Is_Returned() - { - using var sequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = sequence.HasDuplicates(); - - Assert.False(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Sequence_Projection_Without_Duplicates_Then_False_Is_Returned() - { - using var sequence = TestingSequence.Of( - new DummyClass("FirstElement"), - new DummyClass("SecondElement"), - new DummyClass("ThirdElement")); - - var hasDuplicates = sequence.HasDuplicates(x => x.ComparableString); - - Assert.False(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Sequence_With_Duplicates_Then_True_Is_Returned() - { - using var sequence = TestingSequence.Of( - "FirstElement", - "DUPLICATED_STRING", - "DUPLICATED_STRING", - "ThirdElement"); - - var hasDuplicates = sequence.HasDuplicates(); - - Assert.True(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Sequence_Projection_With_Duplicates_Then_True_Is_Returned() - { - using var sequence = TestingSequence.Of( - new DummyClass("FirstElement"), - new DummyClass("DUPLICATED_STRING"), - new DummyClass("DUPLICATED_STRING"), - new DummyClass("ThirdElement")); - - var hasDuplicates = sequence.HasDuplicates(x => x.ComparableString); - - Assert.True(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Sequence_Projection_With_Duplicates_Then_It_Does_Not_Iterate_Unnecessary_On_Elements() - { - using var sequence = TestingSequence.OfWithFailure( - new DummyClass("FirstElement"), - new DummyClass("DUPLICATED_STRING"), - new DummyClass("DUPLICATED_STRING")); - - var hasDuplicates = sequence.HasDuplicates(x => x.ComparableString); - - Assert.True(hasDuplicates); - } - - [Fact] - public void When_Asking_Duplicates_Then_It_Is_Executed_Right_Away() - { - _ = Assert.Throws(() => _ = new BreakingSequence().HasDuplicates()); - } - - [Fact] - public void When_Asking_Duplicates_On_Sequence_Projection_Then_It_Is_Executed_Right_Away() - { - _ = Assert.Throws(() => _ = new BreakingSequence().HasDuplicates(x => x.ComparableString)); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Sequence_With_Custom_Always_True_Comparer_Then_True_Is_Returned() - { - using var sequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = sequence.HasDuplicates(new DummyStringAlwaysTrueComparer()); - - Assert.True(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Sequence_Projection_With_Custom_Always_True_Comparer_Then_True_Is_Returned() - { - using var sequence = TestingSequence.Of( - new DummyClass("FirstElement"), - new DummyClass("SecondElement"), - new DummyClass("ThirdElement")); - - var hasDuplicates = sequence.HasDuplicates(x => x.ComparableString, new DummyStringAlwaysTrueComparer()); - - Assert.True(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_None_Duplicates_Sequence_With_Custom_Always_True_Comparer_Then_True_Is_Returned() - { - using var sequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = sequence.HasDuplicates(new DummyStringAlwaysTrueComparer()); - - Assert.True(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Sequence_With_Null_Comparer_Then_Default_Comparer_Is_Used_And_False_Is_Returned() - { - using var sequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = sequence.HasDuplicates(null); - - Assert.False(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Sequence_With_Custom_Always_False_Comparer_Then_False_Is_Returned() - { - using var sequence = TestingSequence.Of( - "FirstElement", - "SecondElement", - "ThirdElement"); - - var hasDuplicates = sequence.HasDuplicates(new DummyStringAlwaysFalseComparer()); - - Assert.False(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Multiple_Duplicates_Sequence_With_Custom_Always_False_Comparer_Then_False_Is_Returned() - { - using var sequence = TestingSequence.Of( - "DUPLICATED_STRING", - "DUPLICATED_STRING", - "DUPLICATED_STRING"); - - var hasDuplicates = sequence.HasDuplicates(new DummyStringAlwaysFalseComparer()); - - Assert.False(hasDuplicates); - } - - [Fact] - public void When_Asking_For_Duplicates_On_Multiple_Duplicates_Sequence_Projection_With_Custom_Always_False_Comparer_Then_False_Is_Returned() - { - using var sequence = TestingSequence.Of( - new DummyClass("DUPLICATED_STRING"), - new DummyClass("DUPLICATED_STRING"), - new DummyClass("DUPLICATED_STRING")); - - var hasDuplicates = sequence.HasDuplicates(x => x.ComparableString, new DummyStringAlwaysFalseComparer()); - - Assert.False(hasDuplicates); - } - - private sealed class DummyClass - { - public string ComparableString { get; } - - public DummyClass(string comparableString) => ComparableString = comparableString; - } - - private sealed class DummyStringAlwaysTrueComparer : IEqualityComparer - { - public bool Equals(string? x, string? y) => true; - - public int GetHashCode(string obj) => 0; - } - - private sealed class DummyStringAlwaysFalseComparer : IEqualityComparer - { - public bool Equals(string? x, string? y) => false; - - public int GetHashCode(string obj) => 0; + public void DuplicatesDoesNotEnumerateUnnecessarily() + { + using var ts = Seq(1, 2, 3).Concat(SeqExceptionAt(2)).AsTestingSequence(); + + var result = ts.HasDuplicates(); + Assert.True(result); + } + + [Theory] + [InlineData(new int[] { 1, 2, 3, }, false)] + [InlineData(new int[] { 1, 2, 1, 3, 1, 2, 1, }, true)] + [InlineData(new int[] { 3, 3, 2, 2, 1, 1, }, true)] + public void DuplicatesBehavior(IEnumerable source, bool expected) + { + using var ts = source.AsTestingSequence(); + + var result = ts.HasDuplicates(); + Assert.Equal(expected, result); + } + + public static IEnumerable GetStringParameters() + { + yield return new object[] + { + new string[] { "foo", "bar", "qux" }, + StringComparer.Ordinal, + false, + }; + yield return new object[] + { + new string[] { "foo", "FOO", "bar", "qux" }, + StringComparer.Ordinal, + false, + }; + yield return new object[] + { + new string[] { "foo", "FOO", "bar", "qux" }, + StringComparer.OrdinalIgnoreCase, + true, + }; + yield return new object[] + { + new string[] { "Bar", "foo", "FOO", "bar", "qux" }, + StringComparer.OrdinalIgnoreCase, + true, + }; + } + + [Theory] + [MemberData(nameof(GetStringParameters))] + public void DuplicatesComparerBehavior(IEnumerable source, StringComparer comparer, bool expected) + { + using var ts = source.AsTestingSequence(); + + var result = ts.HasDuplicates(comparer); + Assert.Equal(expected, result); } }