Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Duplicates Operator #592

Merged
merged 6 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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