Skip to content
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
80 changes: 80 additions & 0 deletions Containers.Test/OrderedSetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,86 @@ public void LargeDataSet_MaintainsPerformanceAndOrder()
Assert.HasCount(result.Length, result.Distinct(), "All elements should be unique");
}

/// <summary>
/// Builds a set whose notion of equality is case-insensitive, so that the set's own comparer
/// and <see cref="EqualityComparer{T}.Default"/> disagree about which strings are the same.
/// </summary>
private static OrderedSet<string> CaseInsensitiveSet(params string[] items) =>
new(items, StringComparer.OrdinalIgnoreCase);

[TestMethod]
public void IntersectWith_CustomComparer_KeepsElementsThatMatchUnderThatComparer()
{
OrderedSet<string> set = CaseInsensitiveSet("Hello", "World");

set.IntersectWith(["HELLO"]);

Assert.HasCount(1, set, "Only the element matching under the set's comparer should remain");
Assert.IsTrue(set.Contains("Hello"), "\"Hello\" matches \"HELLO\" under the set's comparer and must survive");
}

[TestMethod]
public void IsSubsetOf_CustomComparer_MatchesUnderThatComparer()
{
OrderedSet<string> set = CaseInsensitiveSet("Hello");

Assert.IsTrue(set.IsSubsetOf(["HELLO", "World"]), "\"Hello\" is contained in the collection under the set's comparer");
}

[TestMethod]
public void IsProperSubsetOf_CustomComparer_MatchesUnderThatComparer()
{
OrderedSet<string> set = CaseInsensitiveSet("Hello");

Assert.IsTrue(set.IsProperSubsetOf(["HELLO", "World"]), "The set is a strictly smaller subset under its own comparer");
}

[TestMethod]
public void IsProperSubsetOf_CustomComparer_CountsDistinctElementsUnderThatComparer()
{
OrderedSet<string> set = CaseInsensitiveSet("Hello");

Assert.IsFalse(
set.IsProperSubsetOf(["HELLO", "hello"]),
"The collection holds one distinct element under the set's comparer, so the set is not strictly smaller"
);
}

[TestMethod]
public void IsProperSupersetOf_CustomComparer_CountsDistinctElementsUnderThatComparer()
{
OrderedSet<string> set = CaseInsensitiveSet("Hello", "World");

Assert.IsTrue(
set.IsProperSupersetOf(["HELLO", "hello"]),
"The collection holds one distinct element under the set's comparer, so the set is strictly larger"
);
}

[TestMethod]
public void SetEquals_CustomComparer_CountsDistinctElementsUnderThatComparer()
{
OrderedSet<string> set = CaseInsensitiveSet("Hello", "World");

Assert.IsTrue(
set.SetEquals(["HELLO", "hello", "WORLD"]),
"The collection holds the same two distinct elements under the set's comparer"
);
}

[TestMethod]
public void SymmetricExceptWith_CustomComparer_TreatsMatchesUnderThatComparerAsCommon()
{
OrderedSet<string> set = CaseInsensitiveSet("Hello", "World");

set.SymmetricExceptWith(["HELLO", "Foo"]);

Assert.HasCount(2, set, "The common element should be dropped and the unmatched one added");
Assert.IsFalse(set.Contains("Hello"), "\"Hello\" is common to both under the set's comparer and must be dropped");
Assert.IsTrue(set.Contains("World"), "\"World\" is only in the set and must remain");
Assert.IsTrue(set.Contains("Foo"), "\"Foo\" is only in the other collection and must be added");
}

[TestMethod]
public void StressTest_MultipleOperations()
{
Expand Down
29 changes: 21 additions & 8 deletions Containers/OrderedSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,19 @@ public int BinarySearch(T item)
return ~left; // Return bitwise complement of insertion point
}

/// <summary>
/// Materializes <paramref name="other"/> into a set that uses this set's comparer.
/// </summary>
/// <param name="other">The collection to materialize.</param>
/// <returns>A set containing the distinct elements of <paramref name="other"/>, as this set defines distinctness.</returns>
/// <remarks>
/// The set operations need both membership tests and a cardinality for <paramref name="other"/>.
/// Building a <see cref="HashSet{T}"/> here would answer both with <see cref="EqualityComparer{T}.Default"/>,
/// which disagrees with <see cref="Contains"/> whenever a custom comparer was supplied, so the
/// temporary set is built with <see cref="Comparer"/> instead.
/// </remarks>
private OrderedSet<T> ToComparerSet(IEnumerable<T> other) => new(other, Comparer);

/// <summary>
/// Returns an enumerator that iterates through the set in sorted order.
/// </summary>
Expand Down Expand Up @@ -348,7 +361,7 @@ public void IntersectWith(IEnumerable<T> other)
{
Ensure.NotNull(other);

HashSet<T> otherSet = [.. other];
OrderedSet<T> otherSet = ToComparerSet(other);

for (int i = items.Count - 1; i >= 0; i--)
{
Expand Down Expand Up @@ -383,7 +396,7 @@ public void SymmetricExceptWith(IEnumerable<T> other)
{
Ensure.NotNull(other);

HashSet<T> otherSet = [.. other];
OrderedSet<T> otherSet = ToComparerSet(other);

// Create a list of items to remove from otherSet as we find them
List<T> toRemoveFromOther = [];
Expand Down Expand Up @@ -421,7 +434,7 @@ public bool IsSubsetOf(IEnumerable<T> other)
{
Ensure.NotNull(other);

HashSet<T> otherSet = [.. other];
OrderedSet<T> otherSet = ToComparerSet(other);
return items.All(otherSet.Contains);
}

Expand All @@ -448,8 +461,8 @@ public bool IsProperSubsetOf(IEnumerable<T> other)
{
Ensure.NotNull(other);

HashSet<T> otherSet = [.. other];
return Count < otherSet.Count && IsSubsetOf(otherSet);
OrderedSet<T> otherSet = ToComparerSet(other);
return Count < otherSet.Count && items.All(otherSet.Contains);
}

/// <summary>
Expand All @@ -462,7 +475,7 @@ public bool IsProperSupersetOf(IEnumerable<T> other)
{
Ensure.NotNull(other);

HashSet<T> otherSet = [.. other];
OrderedSet<T> otherSet = ToComparerSet(other);
return Count > otherSet.Count && IsSupersetOf(otherSet);
}

Expand All @@ -489,8 +502,8 @@ public bool SetEquals(IEnumerable<T> other)
{
Ensure.NotNull(other);

HashSet<T> otherSet = [.. other];
return Count == otherSet.Count && IsSubsetOf(otherSet);
OrderedSet<T> otherSet = ToComparerSet(other);
return Count == otherSet.Count && items.All(otherSet.Contains);
}

/// <summary>
Expand Down