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

Introduce Duplicates extension #1037

Merged
merged 52 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
511bcab
introduce duplicates extension
julienasp Nov 14, 2023
6a21b2e
add local function to trigger validation before enumeration
julienasp Nov 14, 2023
ff0c3ca
getting rid of KeySelector
julienasp Nov 14, 2023
79a5e0c
add readme info
julienasp Nov 14, 2023
8db56cd
add entry in csproj
julienasp Nov 14, 2023
00259ce
update coyprights
julienasp Nov 14, 2023
ec22a9f
use null instead of default
julienasp Nov 14, 2023
b62d725
cleanup test
julienasp Nov 14, 2023
35dc3cd
add copyright region
julienasp Nov 14, 2023
277436a
place arrow on the right
julienasp Nov 15, 2023
a5f06e3
apply rename keySet
julienasp Nov 15, 2023
1742515
replace pattern matching with not operator
julienasp Nov 15, 2023
4ed65c9
add isLazy test
julienasp Nov 15, 2023
a760553
change implementation to return at most once of each duplicates
julienasp Nov 15, 2023
c57353c
re-run build.cmd
julienasp Nov 15, 2023
450cda7
fix formating
julienasp Nov 15, 2023
a19b89f
fix spaces
julienasp Nov 15, 2023
2fe88a5
add blanklines
julienasp Nov 15, 2023
3450a3f
update readme
julienasp Nov 15, 2023
c4b2b19
update doc with exception
julienasp Nov 15, 2023
551ff04
rename test cases, fix type
julienasp Nov 15, 2023
9d4c2ee
add blank after summary
julienasp Nov 15, 2023
0e564e5
update doc and regen extension
julienasp Nov 15, 2023
6c29da4
update doc
julienasp Nov 15, 2023
9536cfa
use tsource, add remarks, and rewrite doc
julienasp Nov 15, 2023
85fe597
use same return summary in doc
julienasp Nov 15, 2023
f140e75
fix warns
julienasp Nov 15, 2023
1741a49
move exception doc higher up
julienasp Nov 15, 2023
63a6f6b
try fixing warn
julienasp Nov 15, 2023
ebfac51
use testing sequence instead
julienasp Nov 15, 2023
553ed63
add empty line, ignoring CodeFactor
julienasp Nov 16, 2023
865949d
add streaming test, code from atifaziz
julienasp Nov 16, 2023
4ed9543
use scanby dry
julienasp Nov 17, 2023
83abfc5
regen build
julienasp Nov 17, 2023
bd1c304
Set package validation baseline to version 4.0
atifaziz Nov 15, 2023
22119ee
Use .NET 8 SDK to add .NET 8 target
atifaziz Nov 16, 2023
3d26c34
Update to using C# 12
atifaziz Nov 17, 2023
8401192
Address "Subsets" to-do about clearer members names
atifaziz Nov 18, 2023
c0f6c10
add .net8 unshipped info
julienasp Nov 18, 2023
4235f51
merge master and add unshipped items
julienasp Nov 18, 2023
30df553
checkout master file
julienasp Nov 18, 2023
cafb9ba
add unshipped items
julienasp Nov 18, 2023
61d74d2
Merge branch 'master' into julienasp/add-duplicates
julienasp Nov 18, 2023
3350239
apply pr suggestions
julienasp Nov 19, 2023
c932a23
change assert type
julienasp Nov 19, 2023
416bc59
change assert type
julienasp Nov 19, 2023
61b7da1
using delegate instead of custom class
julienasp Nov 19, 2023
4c1d5dc
update doc
julienasp Nov 19, 2023
7deb085
regen extensions
julienasp Nov 19, 2023
8fd8d85
Update MoreLinq/Duplicates.cs
julienasp Nov 19, 2023
ce6b842
Fix formatting
atifaziz Nov 19, 2023
5095c6f
Simplify comparer for the test
atifaziz Nov 19, 2023
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
220 changes: 220 additions & 0 deletions MoreLinq.Test/DuplicatesTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
namespace MoreLinq.Test
julienasp marked this conversation as resolved.
Show resolved Hide resolved
julienasp marked this conversation as resolved.
Show resolved Hide resolved
{
using System.Collections.Generic;
using NUnit.Framework;

[TestFixture]
public class DuplicatesTest
julienasp marked this conversation as resolved.
Show resolved Hide resolved
{
[Test]
public void When_Asking_For_Duplicates_On_Sequence_Without_Duplicates_Then_Empty_Sequence_Is_Returned()
{
var stringArray = new[]
{
"FirstElement",
"SecondElement",
"ThirdElement"
};

var duplicates = stringArray.Duplicates();

Assert.That(duplicates, Is.Empty);
}

[Test]
public void When_Asking_For_Duplicates_On_Sequence_Projection_Without_Duplicates_Then_Then_Empty_Sequence_Is_Returned()
{
var dummyClasses = new DummyClass[]
{
new("FirstElement"),
new("SecondElement"),
new("ThirdElement")
};

var duplicates = dummyClasses.Duplicates(x => x.ComparableString);

Assert.That(duplicates, Is.Empty);
}

[Test]
public void When_Asking_For_Duplicates_On_Sequence_With_Duplicates_Then_True_Is_Returned()
{
var stringArray = new[]
{
"FirstElement",
"DUPLICATED_STRING",
"DUPLICATED_STRING",
"ThirdElement"
};

var duplicates = stringArray.Duplicates();

Assert.That(duplicates, Contains.Item("DUPLICATED_STRING"));
}

[Test]
public void When_Asking_For_Duplicates_On_Sequence_Projection_With_Duplicates_Then_True_Is_Returned()
{
var dummyClass = new DummyClass("DUPLICATED_STRING");
var dummyClasses = new DummyClass[]
{
new("FirstElement"),
dummyClass,
dummyClass,
new("ThirdElement")
};

var duplicates = dummyClasses.Duplicates(x => x.ComparableString);

Assert.That(duplicates, Contains.Item(dummyClass));
}

[Test]
public void When_Asking_For_Duplicates_On_Sequence_With_Duplicates_Then_It_Does_Not_Iterate_Unnecessary_On_Elements()
{
var source = MoreEnumerable.From(() => "FirstElement",
() => "DUPLICATED_STRING",
() => "DUPLICATED_STRING",
() => throw new TestException());

Assert.DoesNotThrow(() => source.Duplicates());
}

[Test]
public void When_Asking_For_Duplicates_On_Sequence_Projection_With_Duplicates_Then_It_Does_Not_Iterate_Unnecessary_On_Elements()
{
var source = MoreEnumerable.From(() => new DummyClass("FirstElement"),
() => new DummyClass("DUPLICATED_STRING"),
() => new DummyClass("DUPLICATED_STRING"),
() => throw new TestException());

Assert.DoesNotThrow(() => source.Duplicates(x => x.ComparableString));
}

[Test]
public void When_Asking_Duplicates_Then_It_Is_Executed_Right_Away()
julienasp marked this conversation as resolved.
Show resolved Hide resolved
{
_ = Assert.Throws<BreakException>(() => new BreakingSequence<string>().Duplicates().Consume());
}

[Test]
public void When_Asking_Duplicates_On_Sequence_Projection_Then_It_Is_Executed_Right_Away()
julienasp marked this conversation as resolved.
Show resolved Hide resolved
{
_ = Assert.Throws<BreakException>(() => new BreakingSequence<DummyClass>().Duplicates(x => x.ComparableString).Consume());
}

[Test]
public void When_Asking_For_Duplicates_On_Sequence_With_Custom_Always_True_Comparer_Then_True_Is_Returned()
{
var stringArray = new[]
{
"FirstElement",
"SecondElement",
"ThirdElement"
};

var duplicates = stringArray.Duplicates(new DummyStringAlwaysTrueComparer()).ToArray();

Assert.That(duplicates, Contains.Item(stringArray[1]));
Assert.That(duplicates, Contains.Item(stringArray[2]));
}

[Test]
public void When_Asking_For_Duplicates_On_Sequence_Projection_With_Custom_Always_True_Comparer_Then_True_Is_Returned()
{
var dummyClasses = new DummyClass[]
{
new("FirstElement"),
new("SecondElement"),
new("ThirdElement")
};

var duplicates = dummyClasses.Duplicates(x => x.ComparableString, new DummyStringAlwaysTrueComparer()).ToArray();

Assert.That(duplicates, Contains.Item(dummyClasses[1]));
Assert.That(duplicates, Contains.Item(dummyClasses[2]));
}

[Test]
public void When_Asking_For_Duplicates_On_None_Duplicates_Sequence_With_Custom_Always_True_Comparer_Then_True_Is_Returned()
{
var stringArray = new[]
{
"FirstElement",
"SecondElement",
"ThirdElement"
};

var duplicates = stringArray.Duplicates(new DummyStringAlwaysTrueComparer()).ToArray();

Assert.That(duplicates, Contains.Item(stringArray[1]));
Assert.That(duplicates, Contains.Item(stringArray[2]));
}

[Test]
public void When_Asking_For_Duplicates_On_Sequence_With_Custom_Always_False_Comparer_Then_Then_Empty_Sequence_Is_Returned()
julienasp marked this conversation as resolved.
Show resolved Hide resolved
{
var stringArray = new[]
{
"FirstElement",
"SecondElement",
"ThirdElement"
};

var duplicates = stringArray.Duplicates(new DummyStringAlwaysFalseComparer());

Assert.That(duplicates, Is.Empty);
}

[Test]
public void When_Asking_For_Duplicates_On_Multiple_Duplicates_Sequence_With_Custom_Always_False_Comparer_Then_Then_Empty_Sequence_Is_Returned()
julienasp marked this conversation as resolved.
Show resolved Hide resolved
{
var stringArray = new[]
{
"DUPLICATED_STRING",
"DUPLICATED_STRING",
"DUPLICATED_STRING"
};

var duplicates = stringArray.Duplicates(new DummyStringAlwaysFalseComparer());

Assert.That(duplicates, Is.Empty);
}

[Test]
public void When_Asking_For_Duplicates_On_Multiple_Duplicates_Sequence_Projection_With_Custom_Always_False_Comparer_Then_Then_Empty_Sequence_Is_Returned()
{
var dummyClasses = new DummyClass[]
{
new("DUPLICATED_STRING"),
new("DUPLICATED_STRING"),
new("DUPLICATED_STRING")
};

var duplicates = dummyClasses.Duplicates(x => x.ComparableString, new DummyStringAlwaysFalseComparer());

Assert.That(duplicates, Is.Empty);
}

sealed class DummyClass
{
public string ComparableString { get; }

public DummyClass(string comparableString) => ComparableString = comparableString;
}

sealed class DummyStringAlwaysTrueComparer : IEqualityComparer<string>
{
public bool Equals(string? x, string? y) => true;

public int GetHashCode(string obj) => 0;
}

sealed class DummyStringAlwaysFalseComparer : IEqualityComparer<string>
{
public bool Equals(string? x, string? y) => false;

public int GetHashCode(string obj) => 0;
}
}
}
69 changes: 69 additions & 0 deletions MoreLinq/Duplicates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace MoreLinq
julienasp marked this conversation as resolved.
Show resolved Hide resolved
julienasp marked this conversation as resolved.
Show resolved Hide resolved
{
using System;
using System.Collections.Generic;

static partial class MoreEnumerable
{
/// <summary>
/// Returns all duplicated elements of the given source.
julienasp marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="source">source sequence.</param>
julienasp marked this conversation as resolved.
Show resolved Hide resolved
/// <typeparam name="T">The type of the elements in the source sequence.</typeparam>
/// <returns>all elements that are duplicated.</returns>
julienasp marked this conversation as resolved.
Show resolved Hide resolved
julienasp marked this conversation as resolved.
Show resolved Hide resolved
public static IEnumerable<T> Duplicates<T>(this IEnumerable<T> source)
=> Duplicates(source, EqualityComparer<T>.Default);
julienasp marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Returns all duplicated elements of the given source, using the specified element equality comparer.
/// </summary>
/// <param name="source">source sequence.</param>
/// <param name="comparer">The equality comparer to use to determine whether or not keys are equal.
/// If null, the default equality comparer for <c>TSource</c> is used.</param>
/// <typeparam name="T">The type of the elements in the source sequence</typeparam>
/// <returns>all elements of the source sequence that are duplicated, based on the provided equality comparer</returns>
public static IEnumerable<T> Duplicates<T>(this IEnumerable<T> source, IEqualityComparer<T>? comparer)
=> Duplicates(source, IdFn, comparer);
julienasp marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Returns all duplicated elements of the given source, using the specified element equality comparer
/// </summary>
/// <param name="source">source sequence.</param>
/// <param name="keySelector">Projection for determining duplication</param>
/// <typeparam name="TSource">Type of the source sequence</typeparam>
/// <typeparam name="TKey">Type of the projected element</typeparam>
/// <returns>all elements of the source sequence that are duplicated, based on the provided equality comparer</returns>
public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
=> Duplicates(source, keySelector, EqualityComparer<TKey>.Default);
julienasp marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Returns all duplicated elements of the given source, using the specified element equality comparer
julienasp marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="source">source sequence.</param>
julienasp marked this conversation as resolved.
Show resolved Hide resolved
/// <param name="keySelector">Projection for determining duplication</param>
/// <param name="comparer">The equality comparer to use to determine whether or not keys are equal.
/// If null, the default equality comparer for <c>TSource</c> is used.</param>
julienasp marked this conversation as resolved.
Show resolved Hide resolved
/// <typeparam name="TSource">Type of the source sequence</typeparam>
julienasp marked this conversation as resolved.
Show resolved Hide resolved
/// <typeparam name="TKey">Type of the projected element</typeparam>
/// <returns>all elements of the source sequence that are duplicated, based on the provided equality comparer</returns>
julienasp marked this conversation as resolved.
Show resolved Hide resolved
/// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
julienasp marked this conversation as resolved.
Show resolved Hide resolved
/// <exception cref="ArgumentNullException"><paramref name="keySelector"/> is null.</exception>
public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
{
if (source is null) throw new ArgumentNullException(nameof(source));
if (keySelector is null) throw new ArgumentNullException(nameof(keySelector));

return GetDuplicates();

IEnumerable<TSource> GetDuplicates()
{
var enumeratedElements = new HashSet<TKey>(comparer);
julienasp marked this conversation as resolved.
Show resolved Hide resolved
foreach (var element in source)
{
if (enumeratedElements.Add(keySelector(element)) is false)
julienasp marked this conversation as resolved.
Show resolved Hide resolved
julienasp marked this conversation as resolved.
Show resolved Hide resolved
yield return element;
}
}
}
}
}
53 changes: 53 additions & 0 deletions MoreLinq/Extensions.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,59 @@ public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TS

}

/// <summary><c>Duplicates</c> extension.</summary>

[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
public static partial class DuplicatesExtension
{
/// <summary>
/// Returns all duplicated elements of the given source.
/// </summary>
/// <param name="source">source sequence.</param>
/// <typeparam name="T">The type of the elements in the source sequence.</typeparam>
/// <returns>all elements that are duplicated.</returns>
public static IEnumerable<T> Duplicates<T>(this IEnumerable<T> source)
=> MoreEnumerable.Duplicates(source);

/// <summary>
/// Returns all duplicated elements of the given source, using the specified element equality comparer.
/// </summary>
/// <param name="source">source sequence.</param>
/// <param name="comparer">The equality comparer to use to determine whether or not keys are equal.
/// If null, the default equality comparer for <c>TSource</c> is used.</param>
/// <typeparam name="T">The type of the elements in the source sequence</typeparam>
/// <returns>all elements of the source sequence that are duplicated, based on the provided equality comparer</returns>
public static IEnumerable<T> Duplicates<T>(this IEnumerable<T> source, IEqualityComparer<T>? comparer)
=> MoreEnumerable.Duplicates(source, comparer);

/// <summary>
/// Returns all duplicated elements of the given source, using the specified element equality comparer
/// </summary>
/// <param name="source">source sequence.</param>
/// <param name="keySelector">Projection for determining duplication</param>
/// <typeparam name="TSource">Type of the source sequence</typeparam>
/// <typeparam name="TKey">Type of the projected element</typeparam>
/// <returns>all elements of the source sequence that are duplicated, based on the provided equality comparer</returns>
public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
=> MoreEnumerable.Duplicates(source, keySelector);

/// <summary>
/// Returns all duplicated elements of the given source, using the specified element equality comparer
/// </summary>
/// <param name="source">source sequence.</param>
/// <param name="keySelector">Projection for determining duplication</param>
/// <param name="comparer">The equality comparer to use to determine whether or not keys are equal.
/// If null, the default equality comparer for <c>TSource</c> is used.</param>
/// <typeparam name="TSource">Type of the source sequence</typeparam>
/// <typeparam name="TKey">Type of the projected element</typeparam>
/// <returns>all elements of the source sequence that are duplicated, based on the provided equality comparer</returns>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
/// <exception cref="ArgumentNullException"><paramref name="keySelector"/> is null.</exception>
public static IEnumerable<TSource> Duplicates<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer)
=> MoreEnumerable.Duplicates(source, keySelector, comparer);

}

/// <summary><c>EndsWith</c> extension.</summary>

[GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")]
Expand Down
9 changes: 9 additions & 0 deletions MoreLinq/PublicAPI/net6.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
#nullable enable
MoreLinq.Extensions.DuplicatesExtension
static MoreLinq.Extensions.DuplicatesExtension.Duplicates<T>(this System.Collections.Generic.IEnumerable<T>! source) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.Extensions.DuplicatesExtension.Duplicates<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Collections.Generic.IEqualityComparer<T>? comparer) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.Extensions.DuplicatesExtension.Duplicates<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector) -> System.Collections.Generic.IEnumerable<TSource>!
static MoreLinq.Extensions.DuplicatesExtension.Duplicates<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer) -> System.Collections.Generic.IEnumerable<TSource>!
static MoreLinq.MoreEnumerable.Duplicates<T>(this System.Collections.Generic.IEnumerable<T>! source) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.Duplicates<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Collections.Generic.IEqualityComparer<T>? comparer) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.Duplicates<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector) -> System.Collections.Generic.IEnumerable<TSource>!
static MoreLinq.MoreEnumerable.Duplicates<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer) -> System.Collections.Generic.IEnumerable<TSource>!
9 changes: 9 additions & 0 deletions MoreLinq/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
#nullable enable
MoreLinq.Extensions.DuplicatesExtension
static MoreLinq.Extensions.DuplicatesExtension.Duplicates<T>(this System.Collections.Generic.IEnumerable<T>! source) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.Extensions.DuplicatesExtension.Duplicates<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Collections.Generic.IEqualityComparer<T>? comparer) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.Extensions.DuplicatesExtension.Duplicates<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector) -> System.Collections.Generic.IEnumerable<TSource>!
static MoreLinq.Extensions.DuplicatesExtension.Duplicates<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer) -> System.Collections.Generic.IEnumerable<TSource>!
static MoreLinq.MoreEnumerable.Duplicates<T>(this System.Collections.Generic.IEnumerable<T>! source) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.Duplicates<T>(this System.Collections.Generic.IEnumerable<T>! source, System.Collections.Generic.IEqualityComparer<T>? comparer) -> System.Collections.Generic.IEnumerable<T>!
static MoreLinq.MoreEnumerable.Duplicates<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector) -> System.Collections.Generic.IEnumerable<TSource>!
static MoreLinq.MoreEnumerable.Duplicates<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource>! source, System.Func<TSource, TKey>! keySelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer) -> System.Collections.Generic.IEnumerable<TSource>!
Loading