Skip to content

Commit

Permalink
Add Duplicates Operator (#592)
Browse files Browse the repository at this point in the history
* Add sync `Duplicates` operator
* Add async `Duplicates` operator
* Simplify `HasDuplicates` operator
* Simplify `HasDuplicates` tests
* Update front-page readme
* Add linqpad example and link example
  • Loading branch information
viceroypenguin authored Nov 25, 2023
1 parent 27e083f commit a4ff6ab
Show file tree
Hide file tree
Showing 19 changed files with 365 additions and 411 deletions.
Original file line number Diff line number Diff line change
@@ -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-)]
17 changes: 17 additions & 0 deletions Docs/SuperLinq.Docs/apidoc/SuperLinq/Duplicates/Duplicates.linq
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Query Kind="Statements">
<NuGetReference>SuperLinq</NuGetReference>
<Namespace>SuperLinq</Namespace>
</Query>

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]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |

</details>
Expand Down
55 changes: 55 additions & 0 deletions Source/SuperLinq.Async/Duplicates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace SuperLinq.Async;

public static partial class AsyncSuperEnumerable
{
/// <summary>
/// Returns all duplicate elements of the given source.
/// </summary>
/// <typeparam name="TSource">
/// The type of the elements in the source sequence.
/// </typeparam>
/// <param name="source">
/// The source sequence.
/// </param>
/// <param name="comparer">
/// The equality comparer to use to determine whether one <typeparamref name="TSource"/> equals another. If <see
/// langword="null"/>, the default equality comparer for <typeparamref name="TSource"/> is used.
/// </param>
/// <returns>
/// All elements that are duplicated.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/> is <see langword="null"/>.
/// </exception>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// </remarks>
public static IAsyncEnumerable<TSource> Duplicates<TSource>(this IAsyncEnumerable<TSource> source, IEqualityComparer<TSource>? comparer = null)
{
ArgumentNullException.ThrowIfNull(source);

comparer ??= EqualityComparer<TSource>.Default;

return Core(source, comparer);

static async IAsyncEnumerable<TSource> Core(
IAsyncEnumerable<TSource> source,
IEqualityComparer<TSource> comparer,
[EnumeratorCancellation] CancellationToken token = default)
{
var counts = new Collections.NullKeyDictionary<TSource, int>(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;
}
}
}
}
}
21 changes: 4 additions & 17 deletions Source/SuperLinq.Async/HasDuplicates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,9 @@ public static ValueTask<bool> HasDuplicates<TSource, TKey>(this IAsyncEnumerable
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(keySelector);

return Core(source, keySelector, comparer);

static async ValueTask<bool> Core(IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
{
var enumeratedElements = new HashSet<TKey>(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();
}
}

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Func<TKey, TAccumulate>! seedSelector, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, TAccumulate seed, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.Async.AsyncSuperEnumerable.Duplicates<TSource>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Collections.Generic.IEqualityComparer<TSource>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<TSource>!
static SuperLinq.Async.AsyncSuperEnumerable.Partition<T, TResult>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Func<System.Collections.Generic.IEnumerable<T>!, System.Collections.Generic.IEnumerable<T>!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TResult>
static SuperLinq.Async.AsyncSuperEnumerable.Partition<T>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<(System.Collections.Generic.IEnumerable<T>! True, System.Collections.Generic.IEnumerable<T>! False)>
*REMOVED*static SuperLinq.Async.AsyncSuperEnumerable.Partition<T, TResult>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Func<System.Collections.Generic.IAsyncEnumerable<T>!, System.Collections.Generic.IAsyncEnumerable<T>!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TResult>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Func<TKey, TAccumulate>! seedSelector, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, TAccumulate seed, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.Async.AsyncSuperEnumerable.Duplicates<TSource>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Collections.Generic.IEqualityComparer<TSource>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<TSource>!
static SuperLinq.Async.AsyncSuperEnumerable.Partition<T, TResult>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Func<System.Collections.Generic.IEnumerable<T>!, System.Collections.Generic.IEnumerable<T>!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TResult>
static SuperLinq.Async.AsyncSuperEnumerable.Partition<T>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<(System.Collections.Generic.IEnumerable<T>! True, System.Collections.Generic.IEnumerable<T>! False)>
*REMOVED*static SuperLinq.Async.AsyncSuperEnumerable.Partition<T, TResult>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Func<System.Collections.Generic.IAsyncEnumerable<T>!, System.Collections.Generic.IAsyncEnumerable<T>!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TResult>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Func<TKey, TAccumulate>! seedSelector, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, TAccumulate seed, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.Async.AsyncSuperEnumerable.Duplicates<TSource>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Collections.Generic.IEqualityComparer<TSource>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<TSource>!
static SuperLinq.Async.AsyncSuperEnumerable.Partition<T, TResult>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Func<System.Collections.Generic.IEnumerable<T>!, System.Collections.Generic.IEnumerable<T>!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TResult>
static SuperLinq.Async.AsyncSuperEnumerable.Partition<T>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<(System.Collections.Generic.IEnumerable<T>! True, System.Collections.Generic.IEnumerable<T>! False)>
*REMOVED*static SuperLinq.Async.AsyncSuperEnumerable.Partition<T, TResult>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Func<System.Collections.Generic.IAsyncEnumerable<T>!, System.Collections.Generic.IAsyncEnumerable<T>!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TResult>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Func<TKey, TAccumulate>! seedSelector, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.Async.AsyncSuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, TAccumulate seed, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.Async.AsyncSuperEnumerable.Duplicates<TSource>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Collections.Generic.IEqualityComparer<TSource>? comparer = null) -> System.Collections.Generic.IAsyncEnumerable<TSource>!
static SuperLinq.Async.AsyncSuperEnumerable.Partition<T, TResult>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Func<System.Collections.Generic.IEnumerable<T>!, System.Collections.Generic.IEnumerable<T>!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TResult>
static SuperLinq.Async.AsyncSuperEnumerable.Partition<T>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<(System.Collections.Generic.IEnumerable<T>! True, System.Collections.Generic.IEnumerable<T>! False)>
*REMOVED*static SuperLinq.Async.AsyncSuperEnumerable.Partition<T, TResult>(this System.Collections.Generic.IAsyncEnumerable<T>! source, System.Func<T, bool>! predicate, System.Func<System.Collections.Generic.IAsyncEnumerable<T>!, System.Collections.Generic.IAsyncEnumerable<T>!, TResult>! resultSelector, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<TResult>
Expand Down
52 changes: 52 additions & 0 deletions Source/SuperLinq/Duplicates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
namespace SuperLinq;

public static partial class SuperEnumerable
{
/// <summary>
/// Returns all duplicate elements of the given source.
/// </summary>
/// <typeparam name="TSource">
/// The type of the elements in the source sequence.
/// </typeparam>
/// <param name="source">
/// The source sequence.
/// </param>
/// <param name="comparer">
/// The equality comparer to use to determine whether one <typeparamref name="TSource"/> equals another. If <see
/// langword="null"/>, the default equality comparer for <typeparamref name="TSource"/> is used.
/// </param>
/// <returns>
/// All elements that are duplicated.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="source"/> is <see langword="null"/>.
/// </exception>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// </remarks>
public static IEnumerable<TSource> Duplicates<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource>? comparer = null)
{
ArgumentNullException.ThrowIfNull(source);

comparer ??= EqualityComparer<TSource>.Default;

return Core(source, comparer);

static IEnumerable<TSource> Core(IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
{
var counts = new Collections.NullKeyDictionary<TSource, int>(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;
}
}
}
}
}
17 changes: 4 additions & 13 deletions Source/SuperLinq/HasDuplicates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,9 @@ public static bool HasDuplicates<TSource, TKey>(
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(keySelector);

var enumeratedElements = source.TryGetCollectionCount() is { } collectionCount
? new HashSet<TKey>(collectionCount, comparer)
: new HashSet<TKey>(comparer);

foreach (var element in source)
{
if (!enumeratedElements.Add(keySelector(element)))
{
return true;
}
}

return false;
return source
.Select(keySelector)
.Duplicates(comparer)
.Any();
}
}
1 change: 1 addition & 0 deletions Source/SuperLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
*REMOVED*static SuperLinq.SuperEnumerable.Share<TSource, TResult>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<System.Collections.Generic.IEnumerable<TSource>!, System.Collections.Generic.IEnumerable<TResult>!>! selector) -> System.Collections.Generic.IEnumerable<TResult>!
static SuperLinq.SuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Func<TKey, TAccumulate>! seedSelector, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.SuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, TAccumulate seed, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.SuperEnumerable.Duplicates<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Collections.Generic.IEqualityComparer<TSource>? comparer = null) -> System.Collections.Generic.IEnumerable<TSource>!
static SuperLinq.SuperEnumerable.Take<TSource>(System.Collections.Generic.IEnumerable<TSource>! source, System.Range range) -> System.Collections.Generic.IEnumerable<TSource>!
1 change: 1 addition & 0 deletions Source/SuperLinq/PublicAPI/net7.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
*REMOVED*static SuperLinq.SuperEnumerable.Share<TSource, TResult>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<System.Collections.Generic.IEnumerable<TSource>!, System.Collections.Generic.IEnumerable<TResult>!>! selector) -> System.Collections.Generic.IEnumerable<TResult>!
static SuperLinq.SuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Func<TKey, TAccumulate>! seedSelector, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.SuperEnumerable.AggregateBy<TSource, TKey, TAccumulate>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, TAccumulate seed, System.Func<TAccumulate, TSource, TAccumulate>! func, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) -> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TAccumulate>>!
static SuperLinq.SuperEnumerable.Duplicates<TSource>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Collections.Generic.IEqualityComparer<TSource>? comparer = null) -> System.Collections.Generic.IEnumerable<TSource>!
static SuperLinq.SuperEnumerable.Take<TSource>(System.Collections.Generic.IEnumerable<TSource>! source, System.Range range) -> System.Collections.Generic.IEnumerable<TSource>!
Loading

0 comments on commit a4ff6ab

Please sign in to comment.