diff --git a/Containers.Test/OrderedCollectionTests.cs b/Containers.Test/OrderedCollectionTests.cs index 5a0b1c5..dc084e9 100644 --- a/Containers.Test/OrderedCollectionTests.cs +++ b/Containers.Test/OrderedCollectionTests.cs @@ -678,4 +678,67 @@ public void Remove_KeyComparerWithNoExactMatch_RemovesFirstOccurrence() Assert.IsTrue(removed); Assert.AreSequenceEqual(expected, collection); } + + private static readonly IComparer<(int Key, string Name)> ByKey = + Comparer<(int Key, string Name)>.Create((x, y) => x.Key.CompareTo(y.Key)); + + [TestMethod] + public void Add_KeyComparer_KeepsEqualElementsInInsertionOrder() + { + // Act + OrderedCollection<(int Key, string Name)> collection = new(ByKey) + { + (1, "a"), (1, "b"), (1, "c"), (0, "z"), (1, "d"), (2, "y"), (1, "e"), + }; + + // Assert + Assert.AreSequenceEqual( + [(0, "z"), (1, "a"), (1, "b"), (1, "c"), (1, "d"), (1, "e"), (2, "y")], + collection); + } + + [TestMethod] + public void Clone_KeyComparer_IsSequenceEqualToSource() + { + // Arrange + OrderedCollection<(int Key, string Name)> collection = new(ByKey) + { + (1, "a"), (1, "b"), (1, "c"), (0, "z"), (1, "d"), + }; + + // Act + OrderedCollection<(int Key, string Name)> clone = collection.Clone(); + + // Assert + Assert.AreSequenceEqual(collection, clone); + } + + [TestMethod] + public void GetRange_KeyComparer_IsSequenceEqualToSourceRange() + { + // Arrange + OrderedCollection<(int Key, string Name)> collection = new(ByKey) + { + (1, "a"), (1, "b"), (1, "c"), (0, "z"), (1, "d"), + }; + + // Act + OrderedCollection<(int Key, string Name)> range = collection.GetRange(1, 3); + + // Assert + Assert.AreSequenceEqual(collection.Skip(1).Take(3), range); + } + + [TestMethod] + public void Constructor_FromEnumerableWithKeyComparer_PreservesInputOrderOfEqualElements() + { + // Arrange + (int Key, string Name)[] source = [(1, "a"), (1, "b"), (1, "c"), (1, "d"), (1, "e")]; + + // Act + OrderedCollection<(int Key, string Name)> collection = new(source, ByKey); + + // Assert + Assert.AreSequenceEqual(source, collection); + } } diff --git a/Containers/OrderedCollection.cs b/Containers/OrderedCollection.cs index f20c587..eb64144 100644 --- a/Containers/OrderedCollection.cs +++ b/Containers/OrderedCollection.cs @@ -192,17 +192,11 @@ public OrderedCollection(IEnumerable collection, IComparer comparer) /// The element to add. /// /// This operation has O(n) time complexity due to the need to maintain sorted order. - /// The element is inserted at the appropriate position to maintain the sorted sequence. + /// The element is inserted after any elements that compare equal to it, so equal elements + /// keep the order they were added in, and rebuilding a collection from its own sequence + /// (as and do) reproduces that sequence. /// - public void Add(T item) - { - int index = BinarySearch(item); - if (index < 0) - { - index = ~index; // Convert to insertion point - } - items.Insert(index, item); - } + public void Add(T item) => items.Insert(FindUpperBound(item), item); /// /// Removes all elements from the collection. @@ -372,6 +366,32 @@ private int FindFirst(T item) return found; } + /// + /// Binary searches for the position just after the last element that compares equal to the specified element. + /// + /// The element to search for. + /// The index of the first element that compares greater than , or if there is none. + private int FindUpperBound(T item) + { + int left = 0; + int right = items.Count; + + while (left < right) + { + int mid = left + ((right - left) / 2); + if (comparer.Compare(items[mid], item) <= 0) + { + left = mid + 1; + } + else + { + right = mid; + } + } + + return left; + } + /// /// Returns an enumerator that iterates through the collection in sorted order. ///