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
63 changes: 63 additions & 0 deletions Containers.Test/OrderedCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
40 changes: 30 additions & 10 deletions Containers/OrderedCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,17 +192,11 @@ public OrderedCollection(IEnumerable<T> collection, IComparer<T> comparer)
/// <param name="item">The element to add.</param>
/// <remarks>
/// 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 <see cref="Clone"/> and <see cref="GetRange"/> do) reproduces that sequence.
/// </remarks>
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);

/// <summary>
/// Removes all elements from the collection.
Expand Down Expand Up @@ -372,6 +366,32 @@ private int FindFirst(T item)
return found;
}

/// <summary>
/// Binary searches for the position just after the last element that compares equal to the specified element.
/// </summary>
/// <param name="item">The element to search for.</param>
/// <returns>The index of the first element that compares greater than <paramref name="item"/>, or <see cref="Count"/> if there is none.</returns>
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;
}

/// <summary>
/// Returns an enumerator that iterates through the collection in sorted order.
/// </summary>
Expand Down
Loading