diff --git a/Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs b/Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs new file mode 100644 index 00000000..823d0d53 --- /dev/null +++ b/Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs @@ -0,0 +1,92 @@ +using Algorithms.Sorters.Utils; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Algorithms.Tests.Sorters.Utils +{ + [TestFixture] + public class GallopingStrategyTests + { + private readonly IComparer comparer = Comparer.Default; + +[Test] + public void GallopLeft_KeyPresent_ReturnsCorrectIndex() + { + var array = new[] { 1, 2, 3, 4, 5 }; + var index = GallopingStrategy.GallopLeft(array, 3, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(2)); + } + + [Test] + public void GallopLeft_KeyNotPresent_ReturnsCorrectIndex() + { + var array = new[] { 1, 2, 4, 5 }; + var index = GallopingStrategy.GallopLeft(array, 3, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(2)); + } + + [Test] + public void GallopLeft_KeyLessThanAll_ReturnsZero() + { + var array = new[] { 2, 3, 4, 5 }; + var index = GallopingStrategy.GallopLeft(array, 1, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(0)); + } + + [Test] + public void GallopLeft_KeyGreaterThanAll_ReturnsLength() + { + var array = new[] { 1, 2, 3, 4 }; + var index = GallopingStrategy.GallopLeft(array, 5, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(array.Length)); + } + + [Test] + public void GallopRight_KeyPresent_ReturnsCorrectIndex() + { + var array = new[] { 1, 2, 3, 4, 5 }; + var index = GallopingStrategy.GallopRight(array, 3, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(3)); + } + + [Test] + public void GallopRight_KeyNotPresent_ReturnsCorrectIndex() + { + var array = new[] { 1, 2, 4, 5 }; + var index = GallopingStrategy.GallopRight(array, 3, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(2)); + } + + [Test] + public void GallopRight_KeyLessThanAll_ReturnsZero() + { + var array = new[] { 2, 3, 4, 5 }; + var index = GallopingStrategy.GallopRight(array, 1, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(0)); + } + + [Test] + public void GallopRight_KeyGreaterThanAll_ReturnsLength() + { + var array = new[] { 1, 2, 3, 4 }; + var index = GallopingStrategy.GallopRight(array, 5, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(array.Length)); + } + + [Test] + public void GallopLeft_EmptyArray_ReturnsZero() + { + var array = new int[] { }; + var index = GallopingStrategy.GallopLeft(array, 1, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(0)); + } + + [Test] + public void GallopRight_EmptyArray_ReturnsZero() + { + var array = new int[] { }; + var index = GallopingStrategy.GallopRight(array, 1, 0, array.Length, comparer); + Assert.That(index, Is.EqualTo(0)); + } + } +} diff --git a/Algorithms/Sorters/Comparison/TimSorter.cs b/Algorithms/Sorters/Comparison/TimSorter.cs index e2d221fc..a507d980 100755 --- a/Algorithms/Sorters/Comparison/TimSorter.cs +++ b/Algorithms/Sorters/Comparison/TimSorter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Algorithms.Sorters.Utils; namespace Algorithms.Sorters.Comparison; @@ -163,15 +164,6 @@ private static void ReverseRange(T[] array, int start, int end) } } - /// - /// Left shift a value, preventing a roll over to negative numbers. - /// - /// int value to left shift. - /// Left shifted value, bound to 2,147,483,647. - private static int BoundLeftShift(int shiftable) => (shiftable << 1) < 0 - ? (shiftable << 1) + 1 - : int.MaxValue; - /// /// Check the chunks before getting in to a merge to make sure there's something to actually do. /// @@ -270,105 +262,6 @@ private int CountRunAndMakeAscending(T[] array, int start) return runHi - start; } - /// - /// Find the position in the array that a key should fit to the left of where it currently sits. - /// - /// Array to search. - /// Key to place in the array. - /// Base index for the key. - /// Length of the chunk to run through. - /// Initial starting position to start from. - /// Offset for the key's location. - private int GallopLeft(T[] array, T key, int i, int len, int hint) - { - var (offset, lastOfs) = comparer.Compare(key, array[i + hint]) > 0 - ? RightRun(array, key, i, len, hint, 0) - : LeftRun(array, key, i, hint, 1); - - return FinalOffset(array, key, i, offset, lastOfs, 1); - } - - /// - /// Find the position in the array that a key should fit to the right of where it currently sits. - /// - /// Array to search. - /// Key to place in the array. - /// Base index for the key. - /// Length of the chunk to run through. - /// Initial starting position to start from. - /// Offset for the key's location. - private int GallopRight(T[] array, T key, int i, int len, int hint) - { - var (offset, lastOfs) = comparer.Compare(key, array[i + hint]) < 0 - ? LeftRun(array, key, i, hint, 0) - : RightRun(array, key, i, len, hint, -1); - - return FinalOffset(array, key, i, offset, lastOfs, 0); - } - - private (int offset, int lastOfs) LeftRun(T[] array, T key, int i, int hint, int lt) - { - var maxOfs = hint + 1; - var (offset, tmp) = (1, 0); - - while (offset < maxOfs && comparer.Compare(key, array[i + hint - offset]) < lt) - { - tmp = offset; - offset = BoundLeftShift(offset); - } - - if (offset > maxOfs) - { - offset = maxOfs; - } - - var lastOfs = hint - offset; - offset = hint - tmp; - - return (offset, lastOfs); - } - - private (int offset, int lastOfs) RightRun(T[] array, T key, int i, int len, int hint, int gt) - { - var (offset, lastOfs) = (1, 0); - var maxOfs = len - hint; - while (offset < maxOfs && comparer.Compare(key, array[i + hint + offset]) > gt) - { - lastOfs = offset; - offset = BoundLeftShift(offset); - } - - if (offset > maxOfs) - { - offset = maxOfs; - } - - offset += hint; - lastOfs += hint; - - return (offset, lastOfs); - } - - private int FinalOffset(T[] array, T key, int i, int offset, int lastOfs, int lt) - { - lastOfs++; - while (lastOfs < offset) - { - var m = lastOfs + (int)((uint)(offset - lastOfs) >> 1); - - if (comparer.Compare(key, array[i + m]) < lt) - { - offset = m; - } - else - { - lastOfs = m + 1; - } - } - - return offset; - } - /// /// Sorts the specified portion of the specified array using a binary /// insertion sort. It requires O(n log n) compares, but O(n^2) data movement. @@ -470,7 +363,7 @@ private void MergeAt(T[] array, int index) stackSize--; - var k = GallopRight(array, array[baseB], baseA, lenA, 0); + var k = GallopingStrategy.GallopRight(array, array[baseB], baseA, lenA, comparer); baseA += k; lenA -= k; @@ -480,7 +373,7 @@ private void MergeAt(T[] array, int index) return; } - lenB = GallopLeft(array, array[baseA + lenA - 1], baseB, lenB, lenB - 1); + lenB = GallopingStrategy.GallopLeft(array, array[baseA + lenA - 1], baseB, lenB, comparer); if (lenB <= 0) { @@ -595,7 +488,7 @@ private bool StableMerge(TimChunk left, TimChunk right, ref int dest, int private bool GallopMerge(TimChunk left, TimChunk right, ref int dest) { - left.Wins = GallopRight(left.Array, right.Array[right.Index], left.Index, left.Remaining, 0); + left.Wins = GallopingStrategy.GallopRight(left.Array, right.Array[right.Index], left.Index, left.Remaining, comparer); if (left.Wins != 0) { Array.Copy(left.Array, left.Index, right.Array, dest, left.Wins); @@ -614,7 +507,7 @@ private bool GallopMerge(TimChunk left, TimChunk right, ref int dest) return true; } - right.Wins = GallopLeft(right.Array, left.Array[left.Index], right.Index, right.Remaining, 0); + right.Wins = GallopingStrategy.GallopLeft(right.Array, left.Array[left.Index], right.Index, right.Remaining, comparer); if (right.Wins != 0) { Array.Copy(right.Array, right.Index, right.Array, dest, right.Wins); diff --git a/Algorithms/Sorters/Utils/GallopingStrategy.cs b/Algorithms/Sorters/Utils/GallopingStrategy.cs new file mode 100644 index 00000000..7934f7b1 --- /dev/null +++ b/Algorithms/Sorters/Utils/GallopingStrategy.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Algorithms.Sorters.Utils +{ + public static class GallopingStrategy + { + public static int GallopLeft(T[] array, T key, int baseIndex, int length, IComparer comparer) + { + if (array.Length == 0) + { + return 0; + } + + var (offset, lastOfs) = comparer.Compare(key, array[baseIndex]) > 0 + ? RightRun(array, key, baseIndex, length, 0, comparer) + : LeftRun(array, key, baseIndex, 0, comparer); + + return FinalOffset(array, key, baseIndex, offset, lastOfs, 1, comparer); + } + + public static int GallopRight(T[] array, T key, int baseIndex, int length, IComparer comparer) + { + if (array.Length == 0) + { + return 0; + } + + var (offset, lastOfs) = comparer.Compare(key, array[baseIndex]) < 0 + ? LeftRun(array, key, baseIndex, length, comparer) + : RightRun(array, key, baseIndex, length, 0, comparer); + + return FinalOffset(array, key, baseIndex, offset, lastOfs, 0, comparer); + } + + private static (int offset, int lastOfs) LeftRun(T[] array, T key, int baseIndex, int hint, IComparer comparer) + { + var maxOfs = hint + 1; + var (offset, tmp) = (1, 0); + + while (offset < maxOfs && comparer.Compare(key, array[baseIndex + hint - offset]) < 0) + { + tmp = offset; + offset = BoundLeftShift(offset); + } + + if (offset > maxOfs) + { + offset = maxOfs; + } + + var lastOfs = hint - offset; + offset = hint - tmp; + + return (offset, lastOfs); + } + + private static (int offset, int lastOfs) RightRun(T[] array, T key, int baseIndex, int len, int hint, IComparer comparer) + { + var (offset, lastOfs) = (1, 0); + var maxOfs = len - hint; + while (offset < maxOfs && comparer.Compare(key, array[baseIndex + hint + offset]) > 0) + { + lastOfs = offset; + offset = BoundLeftShift(offset); + } + + if (offset > maxOfs) + { + offset = maxOfs; + } + + offset += hint; + lastOfs += hint; + + return (offset, lastOfs); + } + + private static int FinalOffset(T[] array, T key, int baseIndex, int offset, int lastOfs, int lt, IComparer comparer) + { + lastOfs++; + while (lastOfs < offset) + { + var m = lastOfs + (int)((uint)(offset - lastOfs) >> 1); + + if (comparer.Compare(key, array[baseIndex + m]) < lt) + { + offset = m; + } + else + { + lastOfs = m + 1; + } + } + + return offset; + } + + private static int BoundLeftShift(int shiftable) => (shiftable << 1) < 0 + ? (shiftable << 1) + 1 + : int.MaxValue; + } +}