From 22d65fb65a42c3bc0d139f1993e6f3c91d76e175 Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Fri, 25 Aug 2023 10:20:29 -0500 Subject: [PATCH 1/2] Update `DensePartialSortBy` to use `NullKeyDictionary` --- Source/SuperLinq.Async/DensePartialSort.cs | 16 +++++++++------- .../SuperLinq/Collections/NullKeyDictionary.cs | 4 ++++ Source/SuperLinq/DensePartialSort.cs | 16 +++++++++------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Source/SuperLinq.Async/DensePartialSort.cs b/Source/SuperLinq.Async/DensePartialSort.cs index 88fb4a13..29f2b9d0 100644 --- a/Source/SuperLinq.Async/DensePartialSort.cs +++ b/Source/SuperLinq.Async/DensePartialSort.cs @@ -1,4 +1,6 @@ -namespace SuperLinq.Async; +using SuperLinq.Collections; + +namespace SuperLinq.Async; public static partial class AsyncSuperEnumerable { @@ -249,21 +251,21 @@ public static IAsyncEnumerable DensePartialSortBy( static async IAsyncEnumerable Core(IAsyncEnumerable source, int count, Func keySelector, IComparer comparer, [EnumeratorCancellation] CancellationToken cancellationToken = default) { var top = new SortedSet(comparer); - var dic = new Dictionary<(TKey Key, int Index), List>(count); + var dic = new NullKeyDictionary>(count); await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false)) { var key = keySelector(item); if (top.TryGetValue(key, out var oKey)) { - dic[(oKey, 1)].Add(item); + dic[oKey].Add(item); continue; } if (top.Count < count) { _ = top.Add(key); - dic[(key, 1)] = new() { item, }; + dic[key] = new() { item, }; continue; } @@ -271,15 +273,15 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source, in if (comparer.Compare(key, max) > 0) continue; - _ = dic.Remove((max, 1)); + _ = dic.Remove(max); _ = top.Remove(max); _ = top.Add(key); - dic[(key, 1)] = new() { item, }; + dic[key] = new() { item, }; } foreach (var entry in top) { - foreach (var i in dic[(entry, 1)]) + foreach (var i in dic[entry]) yield return i; } } diff --git a/Source/SuperLinq/Collections/NullKeyDictionary.cs b/Source/SuperLinq/Collections/NullKeyDictionary.cs index ee07b1c4..df94ebf7 100644 --- a/Source/SuperLinq/Collections/NullKeyDictionary.cs +++ b/Source/SuperLinq/Collections/NullKeyDictionary.cs @@ -13,6 +13,10 @@ public NullKeyDictionary(IEqualityComparer? comparer) : base(comparer: ValueTupleEqualityComparer.Create(comparer)) { } + public NullKeyDictionary(int count) + : base(count, comparer: ValueTupleEqualityComparer.Create(default)) + { } + public TValue this[TKey key] { get => this[ValueTuple.Create(key)]; diff --git a/Source/SuperLinq/DensePartialSort.cs b/Source/SuperLinq/DensePartialSort.cs index a6655aac..f941ddf6 100644 --- a/Source/SuperLinq/DensePartialSort.cs +++ b/Source/SuperLinq/DensePartialSort.cs @@ -1,4 +1,6 @@ -namespace SuperLinq; +using SuperLinq.Collections; + +namespace SuperLinq; public static partial class SuperEnumerable { @@ -249,21 +251,21 @@ public static IEnumerable DensePartialSortBy( static IEnumerable Core(IEnumerable source, int count, Func keySelector, IComparer comparer) { var top = new SortedSet(comparer); - var dic = new Dictionary<(TKey Key, int Index), List>(count); + var dic = new NullKeyDictionary>(count); foreach (var item in source) { var key = keySelector(item); if (top.TryGetValue(key, out var oKey)) { - dic[(oKey, 1)].Add(item); + dic[oKey].Add(item); continue; } if (top.Count < count) { _ = top.Add(key); - dic[(key, 1)] = new() { item, }; + dic[key] = new() { item, }; continue; } @@ -271,15 +273,15 @@ static IEnumerable Core(IEnumerable source, int count, Func 0) continue; - _ = dic.Remove((max, 1)); + _ = dic.Remove(max); _ = top.Remove(max); _ = top.Add(key); - dic[(key, 1)] = new() { item, }; + dic[key] = new() { item, }; } foreach (var entry in top) { - foreach (var i in dic[(entry, 1)]) + foreach (var i in dic[entry]) yield return i; } } From b20cd66a6f9815709c11cdf09a60db8f065147ef Mon Sep 17 00:00:00 2001 From: Stuart Turner Date: Fri, 25 Aug 2023 10:20:51 -0500 Subject: [PATCH 2/2] Add tests for `DensePartialSort` stability --- .../DensePartialSortByTest.cs | 39 ++++++++++++++++++ .../DensePartialSortTest.cs | 41 +++++++++++++++++++ .../SuperLinq.Test/DensePartialSortByTest.cs | 39 ++++++++++++++++++ Tests/SuperLinq.Test/DensePartialSortTest.cs | 41 +++++++++++++++++++ 4 files changed, 160 insertions(+) diff --git a/Tests/SuperLinq.Async.Test/DensePartialSortByTest.cs b/Tests/SuperLinq.Async.Test/DensePartialSortByTest.cs index 6b4fdd79..486d6ecd 100644 --- a/Tests/SuperLinq.Async.Test/DensePartialSortByTest.cs +++ b/Tests/SuperLinq.Async.Test/DensePartialSortByTest.cs @@ -64,4 +64,43 @@ public async Task DensePartialSortWithComparer() await sorted.Select(e => e.Key[0]) .AssertSequenceEqual('A', 'A', 'C', 'C', 'E', 'E'); } + + [Fact] + public async Task DensePartialSortByIsStable() + { + await using var list = new[] + { + (key: 5, text: "1"), + (key: 5, text: "2"), + (key: 4, text: "3"), + (key: 4, text: "4"), + (key: 3, text: "5"), + (key: 3, text: "6"), + (key: 2, text: "7"), + (key: 2, text: "8"), + (key: 1, text: "9"), + (key: 1, text: "10"), + }.AsTestingSequence(maxEnumerations: 5); + + var stableSort = new[] + { + (key: 1, text: "9"), + (key: 1, text: "10"), + (key: 2, text: "7"), + (key: 2, text: "8"), + (key: 3, text: "5"), + (key: 3, text: "6"), + (key: 4, text: "3"), + (key: 4, text: "4"), + (key: 5, text: "1"), + (key: 5, text: "2"), + }; + + for (var i = 1; i <= 5; i++) + { + var sorted = list.DensePartialSortBy(i, x => x.key); + await sorted.AssertSequenceEqual( + stableSort.Take(i * 2)); + } + } } diff --git a/Tests/SuperLinq.Async.Test/DensePartialSortTest.cs b/Tests/SuperLinq.Async.Test/DensePartialSortTest.cs index 8f60ebda..c0a10428 100644 --- a/Tests/SuperLinq.Async.Test/DensePartialSortTest.cs +++ b/Tests/SuperLinq.Async.Test/DensePartialSortTest.cs @@ -56,4 +56,45 @@ await xs .Select(s => s[0]) .AssertSequenceEqual('A', 'A', 'C', 'C', 'E', 'E'); } + + [Fact] + public async Task DensePartialSortIsStable() + { + await using var list = new[] + { + (key: 5, text: "1"), + (key: 5, text: "2"), + (key: 4, text: "3"), + (key: 4, text: "4"), + (key: 3, text: "5"), + (key: 3, text: "6"), + (key: 2, text: "7"), + (key: 2, text: "8"), + (key: 1, text: "9"), + (key: 1, text: "10"), + }.AsTestingSequence(maxEnumerations: 5); + + var stableSort = new[] + { + (key: 1, text: "9"), + (key: 1, text: "10"), + (key: 2, text: "7"), + (key: 2, text: "8"), + (key: 3, text: "5"), + (key: 3, text: "6"), + (key: 4, text: "3"), + (key: 4, text: "4"), + (key: 5, text: "1"), + (key: 5, text: "2"), + }; + + var comparer = Comparer<(int key, string text)>.Create((a, b) => a.key.CompareTo(b.key)); + + for (var i = 1; i <= 5; i++) + { + var sorted = list.DensePartialSort(i, comparer); + await sorted.AssertSequenceEqual( + stableSort.Take(i * 2)); + } + } } diff --git a/Tests/SuperLinq.Test/DensePartialSortByTest.cs b/Tests/SuperLinq.Test/DensePartialSortByTest.cs index 0a6a8e39..aa51f67d 100644 --- a/Tests/SuperLinq.Test/DensePartialSortByTest.cs +++ b/Tests/SuperLinq.Test/DensePartialSortByTest.cs @@ -64,4 +64,43 @@ public void DensePartialSortWithComparer() .Select(e => e.Key[0]) .AssertSequenceEqual('A', 'A', 'C', 'C', 'E', 'E'); } + + [Fact] + public void DensePartialSortByIsStable() + { + using var list = new[] + { + (key: 5, text: "1"), + (key: 5, text: "2"), + (key: 4, text: "3"), + (key: 4, text: "4"), + (key: 3, text: "5"), + (key: 3, text: "6"), + (key: 2, text: "7"), + (key: 2, text: "8"), + (key: 1, text: "9"), + (key: 1, text: "10"), + }.AsTestingSequence(maxEnumerations: 5); + + var stableSort = new[] + { + (key: 1, text: "9"), + (key: 1, text: "10"), + (key: 2, text: "7"), + (key: 2, text: "8"), + (key: 3, text: "5"), + (key: 3, text: "6"), + (key: 4, text: "3"), + (key: 4, text: "4"), + (key: 5, text: "1"), + (key: 5, text: "2"), + }; + + for (var i = 1; i <= 5; i++) + { + var sorted = list.DensePartialSortBy(i, x => x.key); + sorted.AssertSequenceEqual( + stableSort.Take(i * 2)); + } + } } diff --git a/Tests/SuperLinq.Test/DensePartialSortTest.cs b/Tests/SuperLinq.Test/DensePartialSortTest.cs index c404faf2..c7c03382 100644 --- a/Tests/SuperLinq.Test/DensePartialSortTest.cs +++ b/Tests/SuperLinq.Test/DensePartialSortTest.cs @@ -56,4 +56,45 @@ public void DensePartialSortWithComparer() .Select(s => s[0]) .AssertSequenceEqual('A', 'A', 'C', 'C', 'E', 'E'); } + + [Fact] + public void DensePartialSortIsStable() + { + using var list = new[] + { + (key: 5, text: "1"), + (key: 5, text: "2"), + (key: 4, text: "3"), + (key: 4, text: "4"), + (key: 3, text: "5"), + (key: 3, text: "6"), + (key: 2, text: "7"), + (key: 2, text: "8"), + (key: 1, text: "9"), + (key: 1, text: "10"), + }.AsTestingSequence(maxEnumerations: 5); + + var stableSort = new[] + { + (key: 1, text: "9"), + (key: 1, text: "10"), + (key: 2, text: "7"), + (key: 2, text: "8"), + (key: 3, text: "5"), + (key: 3, text: "6"), + (key: 4, text: "3"), + (key: 4, text: "4"), + (key: 5, text: "1"), + (key: 5, text: "2"), + }; + + var comparer = Comparer<(int key, string text)>.Create((a, b) => a.key.CompareTo(b.key)); + + for (var i = 1; i <= 5; i++) + { + var sorted = list.DensePartialSort(i, comparer); + sorted.AssertSequenceEqual( + stableSort.Take(i * 2)); + } + } }