From d44ae2e00cbc77c8b26649bf02d0f425315c31b1 Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Tue, 12 Jan 2021 17:51:30 -0700 Subject: [PATCH 1/9] Added introsort for 0 or 1 lists of satellite data --- src/Numerics/Sorting.cs | 224 ++++++++++++++++++++++++++++------------ 1 file changed, 157 insertions(+), 67 deletions(-) diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index 13475410..b57b7063 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -38,7 +38,7 @@ namespace MathNet.Numerics public static class Sorting { /// - /// Sort a list of keys, in place using the quick sort algorithm using the quick sort algorithm. + /// Sort a list of keys, in place using the quick sort algorithm using the introsort algorithm. /// /// The type of elements in the key list. /// List to sort. @@ -46,42 +46,12 @@ namespace MathNet.Numerics public static void Sort(IList keys, IComparer comparer = null) { int count = keys.Count; - if (count <= 1) - { - return; - } if (null == comparer) { comparer = Comparer.Default; } - if (count == 2) - { - if (comparer.Compare(keys[0], keys[1]) > 0) - { - Swap(keys, 0, 1); - } - return; - } - - // insertion sort - if (count <= 10) - { - for (int i = 1; i < count; i++) - { - var key = keys[i]; - int j = i - 1; - while (j >= 0 && comparer.Compare(keys[j], key) > 0) - { - keys[j + 1] = keys[j]; - j--; - } - keys[j + 1] = key; - } - return; - } - // array case if (keys is T[] keysArray) { @@ -97,7 +67,7 @@ namespace MathNet.Numerics } // local sort implementation - QuickSort(keys, comparer, 0, count - 1); + IntroSort(keys, null, comparer, 0, count - 1); } /// @@ -111,46 +81,12 @@ namespace MathNet.Numerics public static void Sort(IList keys, IList items, IComparer comparer = null) { int count = keys.Count; - if (count <= 1) - { - return; - } if (null == comparer) { comparer = Comparer.Default; } - if (count == 2) - { - if (comparer.Compare(keys[0], keys[1]) > 0) - { - Swap(keys, 0, 1); - Swap(items, 0, 1); - } - return; - } - - // insertion sort - if (count <= 10) - { - for (int i = 1; i < count; i++) - { - var key = keys[i]; - var item = items[i]; - int j = i - 1; - while (j >= 0 && comparer.Compare(keys[j], key) > 0) - { - keys[j + 1] = keys[j]; - items[j + 1] = items[j]; - j--; - } - keys[j + 1] = key; - items[j + 1] = item; - } - return; - } - // array case if (keys is TKey[] keysArray && items is TItem[] itemsArray) { @@ -159,7 +95,7 @@ namespace MathNet.Numerics } // local sort implementation - QuickSort(keys, items, comparer, 0, count - 1); + IntroSort(keys, items, comparer, 0, count - 1); } /// @@ -470,6 +406,132 @@ namespace MathNet.Numerics QuickSortAll(primary, secondary, primaryComparer, secondaryComparer, 0, primary.Count - 1); } + /// + /// Recursively reorders the given list to satisfy the max heap property. + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// The list which is turned into a heap. + /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. + /// The method with which to compare two elements of the heap. + /// The index of the heap to heapify. + /// The left boundary of the heapify. + /// The right boundary of the heapify. + static void MaxHeapify(IList keys, IList satelliteData, IComparer comparer, int i, int left, int right) + { + int leftChild = 2 * i + 1; + int rightChild = 2 * i + 2; + + int largest = i; + + if (leftChild >= left && leftChild <= right && comparer.Compare(keys[leftChild], keys[largest]) > 0) + { + largest = leftChild; + } + + if (rightChild >= left && rightChild <= right && comparer.Compare(keys[rightChild], keys[largest]) > 0) + { + largest = rightChild; + } + + if (largest != i) + { + Swap(keys, satelliteData, largest, i); + MaxHeapify(keys, satelliteData, comparer, largest, left, right); + } + } + + /// + /// + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// The list which is turned into a heap. + /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. + /// The method with which to compare two elements of the heap. + /// The left boundary of the heap. + /// The right boundary of the heap. + + static void BuildMaxHeap(IList keys, IList satelliteData, IComparer comparer, int left, int right) + { + for (int i = (right - left) / 2; i >= 0; i--) + { + MaxHeapify(keys, satelliteData, comparer, left + i, left, right); + } + } + + /// + /// Recursive implementation for an in place introspective sort on a list. + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// The list which is sorted using intro sort. + /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. + /// The method with which to compare two elements of the intro sort. + /// The left boundary of the intro sort. + /// The right boundary of the intro sort. + /// Tracks the number of recursions entered. + static void IntroSort(IList keys, IList satelliteData, IComparer comparer, int left, int right, int recursions = 0) + { + if (left >= right) + { + return; + } + + Random.CryptoRandomSource rand = new MathNet.Numerics.Random.CryptoRandomSource(); + double ln2 = Math.Log(2); + double max_recursion_depth = 2 * Math.Log(keys.Count) / ln2; // This is the cap on recursion depth used by the GNU STL + + if (right - left < 16) // Insertion Sort is faster on very small sequences + { + for (int i = left + 1; i <= right; i++) + { + int j = i; + while (j > 0 && comparer.Compare(keys[j - 1], keys[j]) > 0) + { + Swap(keys, satelliteData, j, j - 1); + j--; + } + } + } + else if (recursions > max_recursion_depth) // Heapsort is guaranteed O(n log n) + { + BuildMaxHeap(keys, satelliteData, comparer, left, right); + + int heapBoundary = right; + + for (int i = right - left; i > 0; i--) + { + Swap(keys, satelliteData, left, left + i); + MaxHeapify(keys, satelliteData, comparer, left, left, --heapBoundary); + } + } + else // Quicksort + { + int pivot_index = (rand.Next() % (right - left)) + left; // Don't need to worry about negatives because rand.Next() returns a non-negative number + Swap(keys, satelliteData, pivot_index, right); + + TKey pivot = keys[right]; + + int i = left - 1; + for (int j = left; j < right; j++) + { + if (comparer.Compare(keys[j], pivot) <= 0) + { + i++; + Swap(keys, satelliteData, i, j); + } + } + + i++; + Swap(keys, satelliteData, i, right); + + recursions++; + IntroSort(keys, satelliteData, comparer, left, i - 1, recursions); + IntroSort(keys, satelliteData, comparer, i + 1, right, recursions); + } + + } /// /// Recursive implementation for an in place quick sort on a list. @@ -862,5 +924,33 @@ namespace MathNet.Numerics keys[a] = keys[b]; keys[b] = local; } + + /// + /// Performs an in place swap of two elements in a list. + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// The list in which the elements are stored. + /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. + /// The index of the first element of the swap. + /// The index of the second element of the swap. + static void Swap(IList keys, IList satelliteData, int a, int b) + { + if (a == b) + { + return; + } + + TKey local = keys[a]; + keys[a] = keys[b]; + keys[b] = local; + + if (satelliteData != null) + { + TItem tmp = satelliteData[a]; + satelliteData[a] = satelliteData[b]; + satelliteData[b] = tmp; + } + } } } From bd048e23207e60fb5b3c3d32101c3aefcece3a9f Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Tue, 12 Jan 2021 17:52:35 -0700 Subject: [PATCH 2/9] Updated sorting tests to test MathNet sorting --- src/Numerics.Tests/SortingTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Numerics.Tests/SortingTests.cs b/src/Numerics.Tests/SortingTests.cs index c60c1136..4e8f4995 100644 --- a/src/Numerics.Tests/SortingTests.cs +++ b/src/Numerics.Tests/SortingTests.cs @@ -175,7 +175,8 @@ namespace MathNet.Numerics.UnitTests } // default sorting (Ascending) - list.Sort(); + //list.Sort(); + Sorting.Sort(list); // just check that the order is as expected, not that the items are correct for (var i = 1; i < list.Count; i++) From bc330397eac932a56152bd2f43cc183b7152fe95 Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Tue, 12 Jan 2021 18:11:38 -0700 Subject: [PATCH 3/9] Add warning about heapsort implementation --- src/Numerics/Sorting.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index b57b7063..f69dc3e6 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -496,6 +496,9 @@ namespace MathNet.Numerics } else if (recursions > max_recursion_depth) // Heapsort is guaranteed O(n log n) { + // NOTE, there is something wrong with this heapsort implementation. I don't know what, but this fails when the max recursion depth is set too low. + + BuildMaxHeap(keys, satelliteData, comparer, left, right); int heapBoundary = right; From 3447dc90bd89a53c3daa1210f841bdd0efd1b05c Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Tue, 12 Jan 2021 20:12:47 -0700 Subject: [PATCH 4/9] Fixed issue with HeapSort on partitions where left != 0 --- src/Numerics/Sorting.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index f69dc3e6..684c24a3 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -419,25 +419,26 @@ namespace MathNet.Numerics /// The right boundary of the heapify. static void MaxHeapify(IList keys, IList satelliteData, IComparer comparer, int i, int left, int right) { + i -= left; int leftChild = 2 * i + 1; int rightChild = 2 * i + 2; int largest = i; - if (leftChild >= left && leftChild <= right && comparer.Compare(keys[leftChild], keys[largest]) > 0) + if (leftChild + left >= left && leftChild + left <= right && comparer.Compare(keys[leftChild + left], keys[largest + left]) > 0) { largest = leftChild; } - if (rightChild >= left && rightChild <= right && comparer.Compare(keys[rightChild], keys[largest]) > 0) + if (rightChild + left >= left && rightChild + left <= right && comparer.Compare(keys[rightChild + left], keys[largest + left]) > 0) { largest = rightChild; } if (largest != i) { - Swap(keys, satelliteData, largest, i); - MaxHeapify(keys, satelliteData, comparer, largest, left, right); + Swap(keys, satelliteData, largest + left, i + left); + MaxHeapify(keys, satelliteData, comparer, largest + left, left, right); } } @@ -456,7 +457,7 @@ namespace MathNet.Numerics { for (int i = (right - left) / 2; i >= 0; i--) { - MaxHeapify(keys, satelliteData, comparer, left + i, left, right); + MaxHeapify(keys, satelliteData, comparer, i + left, left, right); } } @@ -496,9 +497,6 @@ namespace MathNet.Numerics } else if (recursions > max_recursion_depth) // Heapsort is guaranteed O(n log n) { - // NOTE, there is something wrong with this heapsort implementation. I don't know what, but this fails when the max recursion depth is set too low. - - BuildMaxHeap(keys, satelliteData, comparer, left, right); int heapBoundary = right; From bd8cddeabe615528608ee27139574e693ff14e81 Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Tue, 12 Jan 2021 20:42:50 -0700 Subject: [PATCH 5/9] Modifies comment which erroneously referred to QuickSort --- src/Numerics/Sorting.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index 684c24a3..ca6da850 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -71,7 +71,7 @@ namespace MathNet.Numerics } /// - /// Sort a list of keys and items with respect to the keys, in place using the quick sort algorithm. + /// Sort a list of keys and items with respect to the keys, in place using the introsort algorithm. /// /// The type of elements in the key list. /// The type of elements in the item list. From 67fc94f00a9739c89c9f200ee1f9c32a000d23a7 Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Tue, 12 Jan 2021 20:45:47 -0700 Subject: [PATCH 6/9] Document magic numbers in IntroSort --- src/Numerics/Sorting.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index ca6da850..bebaeada 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -474,16 +474,16 @@ namespace MathNet.Numerics /// Tracks the number of recursions entered. static void IntroSort(IList keys, IList satelliteData, IComparer comparer, int left, int right, int recursions = 0) { + const double ln2 = 0.69314718056; // Natural Logarithm of 2 if (left >= right) { return; } Random.CryptoRandomSource rand = new MathNet.Numerics.Random.CryptoRandomSource(); - double ln2 = Math.Log(2); double max_recursion_depth = 2 * Math.Log(keys.Count) / ln2; // This is the cap on recursion depth used by the GNU STL - if (right - left < 16) // Insertion Sort is faster on very small sequences + if (right - left < 16) // Insertion Sort is faster on very small sequences, 16 is the number that Array.Sort uses { for (int i = left + 1; i <= right; i++) { From 36ce7ab94850c89673d08e3e79d4f739d7734dbc Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Wed, 13 Jan 2021 09:30:32 -0700 Subject: [PATCH 7/9] Generalized IntroSort to several satellite lists --- src/Numerics/Sorting.cs | 254 ++++++++-------------------------------- 1 file changed, 46 insertions(+), 208 deletions(-) diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index bebaeada..ba223ae5 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -67,7 +67,7 @@ namespace MathNet.Numerics } // local sort implementation - IntroSort(keys, null, comparer, 0, count - 1); + IntroSort(keys, comparer, 0, count - 1, (int a, int b) => Swap(keys, a, b)); } /// @@ -95,7 +95,12 @@ namespace MathNet.Numerics } // local sort implementation - IntroSort(keys, items, comparer, 0, count - 1); + Action swap = (int a, int b) => + { + Swap(keys, a, b); + Swap(items, a, b); + }; + IntroSort(keys, comparer, 0, count - 1, swap); } /// @@ -111,52 +116,20 @@ namespace MathNet.Numerics public static void Sort(IList keys, IList items1, IList items2, IComparer comparer = null) { int count = keys.Count; - if (count <= 1) - { - return; - } if (null == comparer) { comparer = Comparer.Default; } - if (count == 2) - { - if (comparer.Compare(keys[0], keys[1]) > 0) - { - Swap(keys, 0, 1); - Swap(items1, 0, 1); - Swap(items2, 0, 1); - } - return; - } - - // insertion sort - if (count <= 10) - { - for (int i = 1; i < count; i++) - { - var key = keys[i]; - var item1 = items1[i]; - var item2 = items2[i]; - int j = i - 1; - while (j >= 0 && comparer.Compare(keys[j], key) > 0) - { - keys[j + 1] = keys[j]; - items1[j + 1] = items1[j]; - items2[j + 1] = items2[j]; - j--; - } - keys[j + 1] = key; - items1[j + 1] = item1; - items2[j + 1] = item2; - } - return; - } - // local sort implementation - QuickSort(keys, items1, items2, comparer, 0, count - 1); + Action swap = (int a, int b) => + { + Swap(keys, a, b); + Swap(items1, a, b); + Swap(items2, a, b); + }; + IntroSort(keys, comparer, 0, count - 1, swap); } /// @@ -179,43 +152,11 @@ namespace MathNet.Numerics throw new ArgumentOutOfRangeException(nameof(count)); } - if (count <= 1) - { - return; - } - if (null == comparer) { comparer = Comparer.Default; } - if (count == 2) - { - if (comparer.Compare(keys[index], keys[index + 1]) > 0) - { - Swap(keys, index, index + 1); - } - return; - } - - // insertion sort - if (count <= 10) - { - int to = index + count; - for (int i = index + 1; i < to; i++) - { - var key = keys[i]; - int j = i - 1; - while (j >= index && comparer.Compare(keys[j], key) > 0) - { - keys[j + 1] = keys[j]; - j--; - } - keys[j + 1] = key; - } - return; - } - // array case if (keys is T[] keysArray) { @@ -231,7 +172,7 @@ namespace MathNet.Numerics } // fall back: local sort implementation - QuickSort(keys, comparer, index, count - 1); + IntroSort(keys, comparer, index, index + count, (int a, int b) => Swap(keys, a, b)); } /// @@ -256,56 +197,18 @@ namespace MathNet.Numerics throw new ArgumentOutOfRangeException(nameof(count)); } - if (count <= 1) - { - return; - } - if (null == comparer) { comparer = Comparer.Default; } - if (count == 2) - { - if (comparer.Compare(keys[index], keys[index + 1]) > 0) - { - Swap(keys, index, index + 1); - Swap(items, index, index + 1); - } - return; - } - - // insertion sort - if (count <= 10) - { - int to = index + count; - for (int i = index + 1; i < to; i++) - { - var key = keys[i]; - var item = items[i]; - int j = i - 1; - while (j >= index && comparer.Compare(keys[j], key) > 0) - { - keys[j + 1] = keys[j]; - items[j + 1] = items[j]; - j--; - } - keys[j + 1] = key; - items[j + 1] = item; - } - return; - } - - // array case - if (keys is TKey[] keysArray && items is TItem[] itemsArray) - { - Array.Sort(keysArray, itemsArray, index, count, comparer); - return; - } - // fall back: local sort implementation - QuickSort(keys, items, comparer, index, count - 1); + Action swap = (int a, int b) => + { + Swap(keys, a, b); + Swap(items, a, b); + }; + IntroSort(keys, comparer, index, index + count, swap); } /// @@ -332,53 +235,19 @@ namespace MathNet.Numerics throw new ArgumentOutOfRangeException(nameof(count)); } - if (count <= 1) - { - return; - } - if (null == comparer) { comparer = Comparer.Default; } - if (count == 2) - { - if (comparer.Compare(keys[index], keys[index + 1]) > 0) - { - Swap(keys, index, index + 1); - Swap(items1, index, index + 1); - Swap(items2, index, index + 1); - } - return; - } - - // insertion sort - if (count <= 10) - { - int to = index + count; - for (int i = index + 1; i < to; i++) - { - var key = keys[i]; - var item1 = items1[i]; - var item2 = items2[i]; - int j = i - 1; - while (j >= index && comparer.Compare(keys[j], key) > 0) - { - keys[j + 1] = keys[j]; - items1[j + 1] = items1[j]; - items2[j + 1] = items2[j]; - j--; - } - keys[j + 1] = key; - items1[j + 1] = item1; - items2[j + 1] = item2; - } - return; - } - // fall back: local sort implementation - QuickSort(keys, items1, items2, comparer, index, count - 1); + Action swap = (int a, int b) => + { + Swap(keys, a, b); + Swap(items1, a, b); + Swap(items2, a, b); + }; + IntroSort(keys, comparer, index, index + count, swap); } /// @@ -410,14 +279,13 @@ namespace MathNet.Numerics /// Recursively reorders the given list to satisfy the max heap property. /// /// The type of elements in the key list. - /// The type of elements in the item list. /// The list which is turned into a heap. - /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. /// The method with which to compare two elements of the heap. /// The index of the heap to heapify. /// The left boundary of the heapify. /// The right boundary of the heapify. - static void MaxHeapify(IList keys, IList satelliteData, IComparer comparer, int i, int left, int right) + /// An Action which takes the given indexes and swaps the key and satellite data. + static void MaxHeapify(IList keys, IComparer comparer, int i, int left, int right, Action swapper) { i -= left; int leftChild = 2 * i + 1; @@ -437,8 +305,8 @@ namespace MathNet.Numerics if (largest != i) { - Swap(keys, satelliteData, largest + left, i + left); - MaxHeapify(keys, satelliteData, comparer, largest + left, left, right); + swapper(largest + left, i + left); + MaxHeapify(keys, comparer, largest + left, left, right, swapper); } } @@ -446,18 +314,17 @@ namespace MathNet.Numerics /// /// /// The type of elements in the key list. - /// The type of elements in the item list. /// The list which is turned into a heap. - /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. /// The method with which to compare two elements of the heap. /// The left boundary of the heap. /// The right boundary of the heap. + /// An Action which takes the given indexes and swaps the key and satellite data. - static void BuildMaxHeap(IList keys, IList satelliteData, IComparer comparer, int left, int right) + static void BuildMaxHeap(IList keys, IComparer comparer, int left, int right, Action swapper) { for (int i = (right - left) / 2; i >= 0; i--) { - MaxHeapify(keys, satelliteData, comparer, i + left, left, right); + MaxHeapify(keys, comparer, i + left, left, right, swapper); } } @@ -465,14 +332,13 @@ namespace MathNet.Numerics /// Recursive implementation for an in place introspective sort on a list. /// /// The type of elements in the key list. - /// The type of elements in the item list. /// The list which is sorted using intro sort. - /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. /// The method with which to compare two elements of the intro sort. /// The left boundary of the intro sort. /// The right boundary of the intro sort. + /// An Action which takes the given indexes and swaps the key and satellite data. /// Tracks the number of recursions entered. - static void IntroSort(IList keys, IList satelliteData, IComparer comparer, int left, int right, int recursions = 0) + static void IntroSort(IList keys, IComparer comparer, int left, int right, Action swapper, int recursions = 0) { const double ln2 = 0.69314718056; // Natural Logarithm of 2 if (left >= right) @@ -490,27 +356,27 @@ namespace MathNet.Numerics int j = i; while (j > 0 && comparer.Compare(keys[j - 1], keys[j]) > 0) { - Swap(keys, satelliteData, j, j - 1); + swapper(j, j - 1); j--; } } } else if (recursions > max_recursion_depth) // Heapsort is guaranteed O(n log n) { - BuildMaxHeap(keys, satelliteData, comparer, left, right); + BuildMaxHeap(keys, comparer, left, right, swapper); int heapBoundary = right; for (int i = right - left; i > 0; i--) { - Swap(keys, satelliteData, left, left + i); - MaxHeapify(keys, satelliteData, comparer, left, left, --heapBoundary); + swapper(left, left + i); + MaxHeapify(keys, comparer, left, left, --heapBoundary, swapper); } } else // Quicksort { int pivot_index = (rand.Next() % (right - left)) + left; // Don't need to worry about negatives because rand.Next() returns a non-negative number - Swap(keys, satelliteData, pivot_index, right); + swapper(pivot_index, right); TKey pivot = keys[right]; @@ -520,16 +386,16 @@ namespace MathNet.Numerics if (comparer.Compare(keys[j], pivot) <= 0) { i++; - Swap(keys, satelliteData, i, j); + swapper(i, j); } } i++; - Swap(keys, satelliteData, i, right); + swapper(i, right); recursions++; - IntroSort(keys, satelliteData, comparer, left, i - 1, recursions); - IntroSort(keys, satelliteData, comparer, i + 1, right, recursions); + IntroSort(keys, comparer, left, i - 1, swapper, recursions); + IntroSort(keys, comparer, i + 1, right, swapper, recursions); } } @@ -925,33 +791,5 @@ namespace MathNet.Numerics keys[a] = keys[b]; keys[b] = local; } - - /// - /// Performs an in place swap of two elements in a list. - /// - /// The type of elements in the key list. - /// The type of elements in the item list. - /// The list in which the elements are stored. - /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. - /// The index of the first element of the swap. - /// The index of the second element of the swap. - static void Swap(IList keys, IList satelliteData, int a, int b) - { - if (a == b) - { - return; - } - - TKey local = keys[a]; - keys[a] = keys[b]; - keys[b] = local; - - if (satelliteData != null) - { - TItem tmp = satelliteData[a]; - satelliteData[a] = satelliteData[b]; - satelliteData[b] = tmp; - } - } } } From 22c7b74e00f720f5abe4d742484d4d8c08599bdc Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Wed, 13 Jan 2021 09:58:54 -0700 Subject: [PATCH 8/9] Corrected references in comments to quicksort --- src/Numerics/Sorting.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index ba223ae5..7b259164 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -38,7 +38,7 @@ namespace MathNet.Numerics public static class Sorting { /// - /// Sort a list of keys, in place using the quick sort algorithm using the introsort algorithm. + /// Sort a list of keys, in place using the introsort algorithm. /// /// The type of elements in the key list. /// List to sort. @@ -104,7 +104,7 @@ namespace MathNet.Numerics } /// - /// Sort a list of keys, items1 and items2 with respect to the keys, in place using the quick sort algorithm. + /// Sort a list of keys, items1 and items2 with respect to the keys, in place using the introsort algorithm. /// /// The type of elements in the key list. /// The type of elements in the first item list. @@ -133,7 +133,7 @@ namespace MathNet.Numerics } /// - /// Sort a range of a list of keys, in place using the quick sort algorithm. + /// Sort a range of a list of keys, in place using the introsort algorithm. /// /// The type of element in the list. /// List to sort. @@ -176,7 +176,7 @@ namespace MathNet.Numerics } /// - /// Sort a list of keys and items with respect to the keys, in place using the quick sort algorithm. + /// Sort a list of keys and items with respect to the keys, in place using the introsort algorithm. /// /// The type of elements in the key list. /// The type of elements in the item list. @@ -212,7 +212,7 @@ namespace MathNet.Numerics } /// - /// Sort a list of keys, items1 and items2 with respect to the keys, in place using the quick sort algorithm. + /// Sort a list of keys, items1 and items2 with respect to the keys, in place using the introsort algorithm. /// /// The type of elements in the key list. /// The type of elements in the first item list. From a74ee1874b3b12a655b49bc9adfa201d0a9c21f7 Mon Sep 17 00:00:00 2001 From: Benjamin Lehmann Date: Wed, 13 Jan 2021 11:28:34 -0700 Subject: [PATCH 9/9] Reverts move to IntroSort on Sorting.Sort overloads which take index and count --- src/Numerics/Sorting.cs | 138 +++++++++++++++++++++++++++++++++++----- 1 file changed, 121 insertions(+), 17 deletions(-) diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index 7b259164..03e68e14 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -133,7 +133,7 @@ namespace MathNet.Numerics } /// - /// Sort a range of a list of keys, in place using the introsort algorithm. + /// Sort a range of a list of keys, in place using the quick sort algorithm. /// /// The type of element in the list. /// List to sort. @@ -152,11 +152,43 @@ namespace MathNet.Numerics throw new ArgumentOutOfRangeException(nameof(count)); } + if (count <= 1) + { + return; + } + if (null == comparer) { comparer = Comparer.Default; } + if (count == 2) + { + if (comparer.Compare(keys[index], keys[index + 1]) > 0) + { + Swap(keys, index, index + 1); + } + return; + } + + // insertion sort + if (count <= 10) + { + int to = index + count; + for (int i = index + 1; i < to; i++) + { + var key = keys[i]; + int j = i - 1; + while (j >= index && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + j--; + } + keys[j + 1] = key; + } + return; + } + // array case if (keys is T[] keysArray) { @@ -172,11 +204,11 @@ namespace MathNet.Numerics } // fall back: local sort implementation - IntroSort(keys, comparer, index, index + count, (int a, int b) => Swap(keys, a, b)); + QuickSort(keys, comparer, index, count - 1); } /// - /// Sort a list of keys and items with respect to the keys, in place using the introsort algorithm. + /// Sort a list of keys and items with respect to the keys, in place using the quick sort algorithm. /// /// The type of elements in the key list. /// The type of elements in the item list. @@ -197,22 +229,60 @@ namespace MathNet.Numerics throw new ArgumentOutOfRangeException(nameof(count)); } + if (count <= 1) + { + return; + } + if (null == comparer) { comparer = Comparer.Default; } - // fall back: local sort implementation - Action swap = (int a, int b) => + if (count == 2) { - Swap(keys, a, b); - Swap(items, a, b); - }; - IntroSort(keys, comparer, index, index + count, swap); + if (comparer.Compare(keys[index], keys[index + 1]) > 0) + { + Swap(keys, index, index + 1); + Swap(items, index, index + 1); + } + return; + } + + // insertion sort + if (count <= 10) + { + int to = index + count; + for (int i = index + 1; i < to; i++) + { + var key = keys[i]; + var item = items[i]; + int j = i - 1; + while (j >= index && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + items[j + 1] = items[j]; + j--; + } + keys[j + 1] = key; + items[j + 1] = item; + } + return; + } + + // array case + if (keys is TKey[] keysArray && items is TItem[] itemsArray) + { + Array.Sort(keysArray, itemsArray, index, count, comparer); + return; + } + + // fall back: local sort implementation + QuickSort(keys, items, comparer, index, count - 1); } /// - /// Sort a list of keys, items1 and items2 with respect to the keys, in place using the introsort algorithm. + /// Sort a list of keys, items1 and items2 with respect to the keys, in place using the quick sort algorithm. /// /// The type of elements in the key list. /// The type of elements in the first item list. @@ -235,19 +305,53 @@ namespace MathNet.Numerics throw new ArgumentOutOfRangeException(nameof(count)); } + if (count <= 1) + { + return; + } + if (null == comparer) { comparer = Comparer.Default; } - // fall back: local sort implementation - Action swap = (int a, int b) => + if (count == 2) { - Swap(keys, a, b); - Swap(items1, a, b); - Swap(items2, a, b); - }; - IntroSort(keys, comparer, index, index + count, swap); + if (comparer.Compare(keys[index], keys[index + 1]) > 0) + { + Swap(keys, index, index + 1); + Swap(items1, index, index + 1); + Swap(items2, index, index + 1); + } + return; + } + + // insertion sort + if (count <= 10) + { + int to = index + count; + for (int i = index + 1; i < to; i++) + { + var key = keys[i]; + var item1 = items1[i]; + var item2 = items2[i]; + int j = i - 1; + while (j >= index && comparer.Compare(keys[j], key) > 0) + { + keys[j + 1] = keys[j]; + items1[j + 1] = items1[j]; + items2[j + 1] = items2[j]; + j--; + } + keys[j + 1] = key; + items1[j + 1] = item1; + items2[j + 1] = item2; + } + return; + } + + // fall back: local sort implementation + QuickSort(keys, items1, items2, comparer, index, count - 1); } ///