diff --git a/Source/Collections/IListExtensions.Test.cs b/Source/Collections/IListExtensions.Test.cs
index 097e9ba..a6c70e6 100644
--- a/Source/Collections/IListExtensions.Test.cs
+++ b/Source/Collections/IListExtensions.Test.cs
@@ -32,6 +32,27 @@ namespace Nuclex.Support.Collections {
[TestFixture]
internal class IListExtensionsTest {
+ /// Tests whether the insertion sort algorithm works on big lists
+ [Test]
+ public void InsertionSortCanSortBigList() {
+ const int ListSize = 16384;
+
+ var testList = new List(capacity: ListSize);
+ {
+ var random = new Random();
+ for(int index = 0; index < ListSize; ++index) {
+ testList.Add(random.Next());
+ }
+ }
+
+ var testListAsIList = (IList)testList;
+ testListAsIList.InsertionSort();
+
+ for(int index = 1; index < ListSize; ++index) {
+ Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
+ }
+ }
+
/// Tests whether the insertion sort algorithm can be applied to 'Text' property works as expected
[Test]
public void InsertionSortCanSortWholeList() {
@@ -60,6 +81,27 @@ namespace Nuclex.Support.Collections {
);
}
+ /// Tests whether the quicksort algorithm works on big lists
+ [Test]
+ public void QuickSortCanSortBigList() {
+ const int ListSize = 16384;
+
+ var testList = new List(capacity: ListSize);
+ {
+ var random = new Random();
+ for(int index = 0; index < ListSize; ++index) {
+ testList.Add(random.Next());
+ }
+ }
+
+ var testListAsIList = (IList)testList;
+ testListAsIList.QuickSort();
+
+ for(int index = 1; index < ListSize; ++index) {
+ Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
+ }
+ }
+
/// Tests whether the insertion sort algorithm can be applied to 'Text' property works as expected
[Test]
public void QuickSortCanSortWholeList() {
diff --git a/Source/Collections/IListExtensions.cs b/Source/Collections/IListExtensions.cs
index ba46a0d..8f79aa3 100644
--- a/Source/Collections/IListExtensions.cs
+++ b/Source/Collections/IListExtensions.cs
@@ -118,21 +118,38 @@ namespace Nuclex.Support.Collections {
this IList list, int startIndex, int count, IComparer comparer
) {
var remainingPartitions = new Stack();
- remainingPartitions.Push(new Partition(startIndex, startIndex + count - 1));
- while(remainingPartitions.Count > 0) {
- Partition current = remainingPartitions.Pop();
- int leftEnd = current.LeftmostIndex;
- int rightEnd = current.RightmostIndex;
+ int lastIndex = startIndex + count - 1;
+ for(; ; ) {
+ int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
- int pivotIndex = quicksortPartition(list, leftEnd, rightEnd, comparer);
- if(pivotIndex - 1 > leftEnd) {
- remainingPartitions.Push(new Partition(leftEnd, pivotIndex - 1));
- }
- if(pivotIndex + 1 < rightEnd) {
- remainingPartitions.Push(new Partition(pivotIndex + 1, rightEnd));
- }
- }
+ // This block just queues the next partitions left of the pivot point and right
+ // of the pivot point (if they contain at least 2 elements). It's fattened up
+ // a bit by trying to forego the stack and adjusting the startIndex/lastIndex
+ // directly where it's clear the next loop can process these partitions.
+ if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot?
+ if(pivotIndex + 1 < lastIndex) { // Are the elements left of the pivot as well?
+ remainingPartitions.Push(new Partition(startIndex, pivotIndex - 1));
+ startIndex = pivotIndex + 1;
+ } else { // Elements to sort are only right of the pivot
+ lastIndex = pivotIndex - 1;
+ }
+ } else if(pivotIndex + 1 < lastIndex) { // Are elements to sort only left of the pivot?
+ startIndex = pivotIndex + 1;
+ } else { // Partition was fully sorted
+
+ // Did we process all queued partitions? If so, the list is sorted
+ if(remainingPartitions.Count == 0) {
+ return;
+ }
+
+ // Pull the next partition that needs to be sorted from the stack
+ Partition current = remainingPartitions.Pop();
+ startIndex = current.LeftmostIndex;
+ lastIndex = current.RightmostIndex;
+
+ } // if sortable sub-partitions exist left/right/nowhere
+ } // for ever (termination inside loop)
}
///
@@ -156,32 +173,45 @@ namespace Nuclex.Support.Collections {
QuickSort(list, 0, list.Count, Comparer.Default);
}
+ ///
+ /// Moves an element downward over all elements that precede it in the sort order
+ ///
+ /// Type of elements stored in the sorted list
+ /// List that is being sorted
+ /// Index of the first element in the partition
+ /// Index of hte last element in the partition
+ ///
+ /// Comparison function that decides the ordering of elements
+ ///
+ /// The index of the next pivot element
private static int quicksortPartition(
IList list, int firstIndex, int lastIndex, IComparer comparer
) {
- TElement pivot = list[lastIndex];
-
- // Set the high index element to its proper sorted position
- int nextIndex = firstIndex;
+
+ // Step through all elements in the partition and accumulate those that are smaller
+ // than the last element on the left (by swapping). At the end 'firstIndex' will be
+ // the new pivot point, left of which are all elements smaller than the element at
+ // 'lastIndex' and right of it will be all elements which are larger.
for(int index = firstIndex; index < lastIndex; ++index) {
- if(comparer.Compare(list[index], pivot) < 0) {
- TElement temp = list[nextIndex];
- list[nextIndex] = list[index];
+ if(comparer.Compare(list[index], list[lastIndex]) < 0) {
+ TElement temp = list[firstIndex];
+ list[firstIndex] = list[index];
list[index] = temp;
- ++nextIndex;
+ ++firstIndex;
}
}
-
- // Set the high index value to its sorted position
+
+ // The element at 'lastIndex' as a sort value that's in the middle of the two sides,
+ // so we'll have to swap it, too, putting it in the middle and making it the new pivot.
{
- TElement temp = list[nextIndex];
- list[nextIndex] = list[lastIndex];
+ TElement temp = list[firstIndex];
+ list[firstIndex] = list[lastIndex];
list[lastIndex] = temp;
}
- // Returns the next sorting element location
- return nextIndex;
+ // Return the index of the new pivot position
+ return firstIndex;
}