From f928e1e35e9b1c2fe23ebee2b9ad70d0db281ae2 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 3 Sep 2023 13:35:49 +0200 Subject: [PATCH] QuickSort methods extracted to their own extension class. --- .../Linq/QuickSortExtensions.cs | 132 ++++++++++++++++++ .../RecyclableList.Helpers.cs | 118 +--------------- .../RecyclableSortedList.cs | 6 +- .../RecyclableListTests.cs | 3 +- .../RecyclableLongListTests.cs | 3 +- 5 files changed, 141 insertions(+), 121 deletions(-) create mode 100644 Recyclable.Collections/Linq/QuickSortExtensions.cs diff --git a/Recyclable.Collections/Linq/QuickSortExtensions.cs b/Recyclable.Collections/Linq/QuickSortExtensions.cs new file mode 100644 index 0000000..d72dfac --- /dev/null +++ b/Recyclable.Collections/Linq/QuickSortExtensions.cs @@ -0,0 +1,132 @@ +// Ignore Spelling: Linq +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +// The source code was taken from +// https://codereview.stackexchange.com/questions/205407/quicksort-without-recursion. +// +// It was only beautified for .Net 6.0+. + +namespace Recyclable.Collections.Linq +{ + internal static class QuickSortExtensions + { + + public static void InsertionSort(IList values, int startIndex, int endIndex, IComparer comparer) + { + var left = startIndex; + while (left < endIndex) + { + int right = left; + T? temp = values[++left]; + while (right >= startIndex && 0 < comparer.Compare(values[right], temp)) + { + values[right + 1] = values[right--]; + } + + values[right + 1] = temp; + } + } + + public static void InsertionSort(IList values, IComparer comparer) + { + InsertionSort(values, 0, values.Count - 1, comparer); + } + + public static void InsertionSort(IList values) + { + InsertionSort(values, Comparer.Default); + } + + public static void QuickSort(IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) + { + var range = (startIndex, endIndex); + var stack = new Stack<(int, int)>(); + + do + { + startIndex = range.startIndex; + endIndex = range.endIndex; + + if (endIndex - startIndex + 1 < 31) + { + InsertionSort(values, startIndex, endIndex, comparer); + + continue; + } + + var pivot = SampleMedian(values, startIndex, endIndex, comparer, randomNumberGenerator); + var left = startIndex; + var right = endIndex; + + while (left <= right) + { + while (0 > comparer.Compare(values[left], pivot)) + { + left++; + } + + while (0 > comparer.Compare(pivot, values[right])) + { + right--; + } + + if (left <= right) + { + Swap(values, left++, right--); + } + } + + if (startIndex < right) + { + stack.Push((startIndex, right)); + } + + if (left < endIndex) + { + stack.Push((left, endIndex)); + } + } + while (stack.TryPop(out range)); + } + + public static void QuickSort(IList values, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) + => QuickSort(values, 0, values.Count - 1, comparer, randomNumberGenerator); + + public static void QuickSort(IList values) + => QuickSort(values, Comparer.Default, new SystemRandomNumberGenerator()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T SampleMedian(IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) + { + var left = randomNumberGenerator.NextInt32(startIndex, endIndex); + var middle = randomNumberGenerator.NextInt32(startIndex, endIndex); + var right = randomNumberGenerator.NextInt32(startIndex, endIndex); + + if (0 > comparer.Compare(values[right], values[left])) + { + Swap(values, right, left); + } + + if (0 > comparer.Compare(values[middle], values[left])) + { + Swap(values, middle, left); + } + + if (0 > comparer.Compare(values[right], values[middle])) + { + Swap(values, right, middle); + } + + return values[middle]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Swap(IList values, int xIndex, int yIndex) + => (values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]); + } +} diff --git a/Recyclable.Collections/RecyclableList.Helpers.cs b/Recyclable.Collections/RecyclableList.Helpers.cs index 65d7c50..048a08d 100644 --- a/Recyclable.Collections/RecyclableList.Helpers.cs +++ b/Recyclable.Collections/RecyclableList.Helpers.cs @@ -1,6 +1,4 @@ -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using Recyclable.Collections.Pools; namespace Recyclable.Collections @@ -85,32 +83,6 @@ namespace Recyclable.Collections } } - public static void InsertionSort(IList values, int startIndex, int endIndex, IComparer comparer) - { - var left = startIndex; - while (left < endIndex) - { - int right = left; - T? temp = values[++left]; - while (right >= startIndex && 0 < comparer.Compare(values[right], temp)) - { - values[right + 1] = values[right--]; - } - - values[right + 1] = temp; - } - } - - public static void InsertionSort(IList values, IComparer comparer) - { - InsertionSort(values, 0, values.Count - 1, comparer); - } - - public static void InsertionSort(IList values) - { - InsertionSort(values, Comparer.Default); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long LongIndexOf(RecyclableList arrays, int blockSize, T item, IEqualityComparer comparer) { @@ -130,90 +102,6 @@ namespace Recyclable.Collections return -1; } - // The source code was taken from https://codereview.stackexchange.com/questions/205407/quicksort-without-recursion. - // It was only beautified for .Net 6.0+. - public static void QuickSort(IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) - { - var range = (startIndex, endIndex); - var stack = new Stack<(int, int)>(); - - do - { - startIndex = range.startIndex; - endIndex = range.endIndex; - - if (endIndex - startIndex + 1 < 31) - { - InsertionSort(values, startIndex, endIndex, comparer); - - continue; - } - - var pivot = SampleMedian(values, startIndex, endIndex, comparer, randomNumberGenerator); - var left = startIndex; - var right = endIndex; - - while (left <= right) - { - while (0 > comparer.Compare(values[left], pivot)) - { - left++; - } - - while (0 > comparer.Compare(pivot, values[right])) - { - right--; - } - - if (left <= right) - { - Swap(values, left++, right--); - } - } - - if (startIndex < right) - { - stack.Push((startIndex, right)); - } - - if (left < endIndex) - { - stack.Push((left, endIndex)); - } - } - while (stack.TryPop(out range)); - } - public static void QuickSort(IList values, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) - => QuickSort(values, 0, values.Count - 1, comparer, randomNumberGenerator); - - public static void QuickSort(IList values) - => QuickSort(values, Comparer.Default, new SystemRandomNumberGenerator()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T SampleMedian(IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) - { - var left = randomNumberGenerator.NextInt32(startIndex, endIndex); - var middle = randomNumberGenerator.NextInt32(startIndex, endIndex); - var right = randomNumberGenerator.NextInt32(startIndex, endIndex); - - if (0 > comparer.Compare(values[right], values[left])) - { - Swap(values, right, left); - } - - if (0 > comparer.Compare(values[middle], values[left])) - { - Swap(values, middle, left); - } - - if (0 > comparer.Compare(values[right], values[middle])) - { - Swap(values, right, middle); - } - - return values[middle]; - } - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public static void ResizeAndCopy(RecyclableList list) { @@ -267,9 +155,5 @@ namespace Recyclable.Collections return newSize; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Swap(IList values, int xIndex, int yIndex) - => (values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]); } } diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs index b347a5f..9847554 100644 --- a/Recyclable.Collections/RecyclableSortedList.cs +++ b/Recyclable.Collections/RecyclableSortedList.cs @@ -1,4 +1,6 @@ -namespace Recyclable.Collections +using Recyclable.Collections.Linq; + +namespace Recyclable.Collections { internal class RecyclableSortedList : RecyclableLongList<(TKey Key, TValue Value)> where TKey : notnull @@ -68,7 +70,7 @@ BeginUpdate(); try { - RecyclableListHelpers<(TKey, TValue)>.QuickSort(this, _comparer, _randomNumberGenerator); + QuickSortExtensions<(TKey, TValue)>.QuickSort(this, _comparer, _randomNumberGenerator); } finally { diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index e340bfc..8ed02c4 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Recyclable.Collections; +using Recyclable.Collections.Linq; #pragma warning disable xUnit1026, RCS1163, IDE0060, RCS1235 @@ -424,7 +425,7 @@ namespace Recyclable.CollectionsTests // Act using RecyclableList list = new(testData); - RecyclableListHelpers.QuickSort(list); + QuickSortExtensions.QuickSort(list); // Validate _ = list.Count.Should().Be((int)itemsCount); diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index 03dac59..feafdc4 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Recyclable.Collections; +using Recyclable.Collections.Linq; #pragma warning disable xUnit1026, RCS1163, IDE0060, RCS1235 @@ -481,7 +482,7 @@ namespace Recyclable.CollectionsTests // Act using RecyclableLongList list = new(testData, minBlockSize: targetBlockSize); - RecyclableListHelpers.QuickSort(list); + QuickSortExtensions.QuickSort(list); // Validate _ = list.LongCount.Should().Be(itemsCount);