From d97a99f4d80445dc6915ab25c39680bdf35400c5 Mon Sep 17 00:00:00 2001 From: Konstantinos Kalafatis Date: Sun, 22 Sep 2024 13:31:11 +0300 Subject: [PATCH] Extract galloping methods into static GallopingStrategy class - Moved galloping logic (GallopLeft, GallopRight, LeftRun, RightRun, FinalOffset) from TimSorter to a new GallopingStrategy static class. - Simplified the code by removing the interface and making all methods static since there's no need for instance-specific behavior. - The refactored GallopingStrategy class now encapsulates galloping functionality, improving modularity and testability. - Updated TimSorter to use GallopingStrategy for gallop operations, enhancing code clarity and separation of concerns. --- .../Sorters/Comparison/TimSorterTests.cs | 7 +- .../Sorters/Utils/GallopingStrategyTests.cs | 120 +++++++++++++++++ Algorithms/Sorters/Comparison/TimSorter.cs | 121 ++---------------- Algorithms/Sorters/Utils/GallopingStrategy.cs | 106 +++++++++++++++ 4 files changed, 238 insertions(+), 116 deletions(-) create mode 100644 Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs create mode 100644 Algorithms/Sorters/Utils/GallopingStrategy.cs diff --git a/Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs b/Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs index 7605412a..822ac789 100755 --- a/Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs +++ b/Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs @@ -9,13 +9,14 @@ namespace Algorithms.Tests.Sorters.Comparison; public static class TimSorterTests { private static readonly IntComparer IntComparer = new(); + private static readonly TimSorterSettings Settings = new(); [Test] public static void ArraySorted( [Random(0, 10_000, 2000)] int n) { // Arrange - var sorter = new TimSorter(new TimSorterSettings()); + var sorter = new TimSorter(Settings, IntComparer); var (correctArray, testArray) = RandomHelper.GetArrays(n); // Act @@ -30,7 +31,7 @@ public static void ArraySorted( public static void TinyArray() { // Arrange - var sorter = new TimSorter(new TimSorterSettings()); + var sorter = new TimSorter(Settings, IntComparer); var tinyArray = new[] { 1 }; var correctArray = new[] { 1 }; @@ -45,7 +46,7 @@ public static void TinyArray() public static void SmallChunks() { // Arrange - var sorter = new TimSorter(new TimSorterSettings()); + var sorter = new TimSorter(Settings, IntComparer); var (correctArray, testArray) = RandomHelper.GetArrays(800); Array.Sort(correctArray, IntComparer); Array.Sort(testArray, IntComparer); diff --git a/Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs b/Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs new file mode 100644 index 00000000..2c7e6050 --- /dev/null +++ b/Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs @@ -0,0 +1,120 @@ +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)); + } + + // Test when (shiftable << 1) < 0 is true + [Test] + public void TestBoundLeftShift_WhenShiftableCausesNegativeShift_ReturnsShiftedValuePlusOne() + { + // Arrange + int shiftable = int.MaxValue; // This should cause a negative result after left shift + + // Act + int result = GallopingStrategy.BoundLeftShift(shiftable); + + // Assert + Assert.That((shiftable << 1) + 1, Is.EqualTo(result)); // True branch + } + + // Test when (shiftable << 1) < 0 is false + [Test] + public void TestBoundLeftShift_WhenShiftableDoesNotCauseNegativeShift_ReturnsMaxValue() + { + // Arrange + int shiftable = 1; // This will not cause a negative result after left shift + + // Act + int result = GallopingStrategy.BoundLeftShift(shiftable); + + // Assert + Assert.That(int.MaxValue, Is.EqualTo(result)); // False branch + } + } +} diff --git a/Algorithms/Sorters/Comparison/TimSorter.cs b/Algorithms/Sorters/Comparison/TimSorter.cs index e2d221fc..0115e560 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; @@ -54,7 +55,7 @@ private class TimChunk public int Wins { get; set; } } - public TimSorter(TimSorterSettings settings) + public TimSorter(TimSorterSettings settings, IComparer comparer) { initMinGallop = minGallop; runBase = new int[85]; @@ -64,6 +65,8 @@ public TimSorter(TimSorterSettings settings) minGallop = settings.MinGallop; minMerge = settings.MinMerge; + + this.comparer = comparer ?? Comparer.Default; } /// @@ -163,15 +166,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 +264,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 +365,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 +375,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 +490,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 +509,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..2226064b --- /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); + } + + public static int BoundLeftShift(int shiftable) => (shiftable << 1) < 0 + ? (shiftable << 1) + 1 + : int.MaxValue; + + 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; + } + } +}