Skip to content

Commit

Permalink
Extract galloping methods into static GallopingStrategy class
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
Kalkwst committed Sep 22, 2024
1 parent 9439ee2 commit d634a74
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 119 deletions.
13 changes: 7 additions & 6 deletions Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ 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<int>(new TimSorterSettings());
var sorter = new TimSorter<int>(Settings, IntComparer);
var (correctArray, testArray) = RandomHelper.GetArrays(n);

// Act
sorter.Sort(testArray, IntComparer);
sorter.Sort(testArray);

Check failure on line 23 in Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs

View workflow job for this annotation

GitHub Actions / build

There is no argument given that corresponds to the required parameter 'comparer' of 'TimSorter<int>.Sort(int[], IComparer<int>)'

Check failure on line 23 in Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs

View workflow job for this annotation

GitHub Actions / build

There is no argument given that corresponds to the required parameter 'comparer' of 'TimSorter<int>.Sort(int[], IComparer<int>)'
Array.Sort(correctArray, IntComparer);

// Assert
Expand All @@ -30,12 +31,12 @@ public static void ArraySorted(
public static void TinyArray()
{
// Arrange
var sorter = new TimSorter<int>(new TimSorterSettings());
var sorter = new TimSorter<int>(Settings, IntComparer);
var tinyArray = new[] { 1 };
var correctArray = new[] { 1 };

// Act
sorter.Sort(tinyArray, IntComparer);
sorter.Sort(tinyArray);

Check failure on line 39 in Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs

View workflow job for this annotation

GitHub Actions / build

There is no argument given that corresponds to the required parameter 'comparer' of 'TimSorter<int>.Sort(int[], IComparer<int>)'

Check failure on line 39 in Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs

View workflow job for this annotation

GitHub Actions / build

There is no argument given that corresponds to the required parameter 'comparer' of 'TimSorter<int>.Sort(int[], IComparer<int>)'

// Assert
Assert.That(correctArray, Is.EqualTo(tinyArray));
Expand All @@ -45,7 +46,7 @@ public static void TinyArray()
public static void SmallChunks()
{
// Arrange
var sorter = new TimSorter<int>(new TimSorterSettings());
var sorter = new TimSorter<int>(Settings, IntComparer);
var (correctArray, testArray) = RandomHelper.GetArrays(800);
Array.Sort(correctArray, IntComparer);
Array.Sort(testArray, IntComparer);
Expand All @@ -59,7 +60,7 @@ public static void SmallChunks()
testArray[800 - 1] = min;

// Act
sorter.Sort(testArray, IntComparer);
sorter.Sort(testArray);

Check failure on line 63 in Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs

View workflow job for this annotation

GitHub Actions / build

There is no argument given that corresponds to the required parameter 'comparer' of 'TimSorter<int>.Sort(int[], IComparer<int>)'

Check failure on line 63 in Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs

View workflow job for this annotation

GitHub Actions / build

There is no argument given that corresponds to the required parameter 'comparer' of 'TimSorter<int>.Sort(int[], IComparer<int>)'
Array.Sort(correctArray, IntComparer);

// Assert
Expand Down
120 changes: 120 additions & 0 deletions Algorithms.Tests/Sorters/Utils/GallopingStrategyTests.cs
Original file line number Diff line number Diff line change
@@ -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<int> comparer = Comparer<int>.Default;

[Test]
public void GallopLeft_KeyPresent_ReturnsCorrectIndex()
{
var array = new[] { 1, 2, 3, 4, 5 };
var index = GallopingStrategy<int>.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<int>.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<int>.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<int>.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<int>.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<int>.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<int>.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<int>.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<int>.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<int>.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<int>.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<int>.BoundLeftShift(shiftable);

// Assert
Assert.That(int.MaxValue, Is.EqualTo(result)); // False branch
}
}
}
121 changes: 8 additions & 113 deletions Algorithms/Sorters/Comparison/TimSorter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Algorithms.Sorters.Utils;

namespace Algorithms.Sorters.Comparison;

Expand Down Expand Up @@ -54,7 +55,7 @@ private class TimChunk<Tc>
public int Wins { get; set; }
}

public TimSorter(TimSorterSettings settings)
public TimSorter(TimSorterSettings settings, IComparer<T> comparer)
{
initMinGallop = minGallop;
runBase = new int[85];
Expand All @@ -64,6 +65,8 @@ public TimSorter(TimSorterSettings settings)

minGallop = settings.MinGallop;
minMerge = settings.MinMerge;

this.comparer = comparer ?? Comparer<T>.Default;
}

/// <summary>
Expand Down Expand Up @@ -163,15 +166,6 @@ private static void ReverseRange(T[] array, int start, int end)
}
}

/// <summary>
/// Left shift a value, preventing a roll over to negative numbers.
/// </summary>
/// <param name="shiftable">int value to left shift.</param>
/// <returns>Left shifted value, bound to 2,147,483,647.</returns>
private static int BoundLeftShift(int shiftable) => (shiftable << 1) < 0
? (shiftable << 1) + 1
: int.MaxValue;

/// <summary>
/// Check the chunks before getting in to a merge to make sure there's something to actually do.
/// </summary>
Expand Down Expand Up @@ -270,105 +264,6 @@ private int CountRunAndMakeAscending(T[] array, int start)
return runHi - start;
}

/// <summary>
/// Find the position in the array that a key should fit to the left of where it currently sits.
/// </summary>
/// <param name="array">Array to search.</param>
/// <param name="key">Key to place in the array.</param>
/// <param name="i">Base index for the key.</param>
/// <param name="len">Length of the chunk to run through.</param>
/// <param name="hint">Initial starting position to start from.</param>
/// <returns>Offset for the key's location.</returns>
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);
}

/// <summary>
/// Find the position in the array that a key should fit to the right of where it currently sits.
/// </summary>
/// <param name="array">Array to search.</param>
/// <param name="key">Key to place in the array.</param>
/// <param name="i">Base index for the key.</param>
/// <param name="len">Length of the chunk to run through.</param>
/// <param name="hint">Initial starting position to start from.</param>
/// <returns>Offset for the key's location.</returns>
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;
}

/// <summary>
/// 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.
Expand Down Expand Up @@ -470,7 +365,7 @@ private void MergeAt(T[] array, int index)

stackSize--;

var k = GallopRight(array, array[baseB], baseA, lenA, 0);
var k = GallopingStrategy<T>.GallopRight(array, array[baseB], baseA, lenA, comparer);

baseA += k;
lenA -= k;
Expand All @@ -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<T>.GallopLeft(array, array[baseA + lenA - 1], baseB, lenB, comparer);

if (lenB <= 0)
{
Expand Down Expand Up @@ -595,7 +490,7 @@ private bool StableMerge(TimChunk<T> left, TimChunk<T> right, ref int dest, int

private bool GallopMerge(TimChunk<T> left, TimChunk<T> right, ref int dest)
{
left.Wins = GallopRight(left.Array, right.Array[right.Index], left.Index, left.Remaining, 0);
left.Wins = GallopingStrategy<T>.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);
Expand All @@ -614,7 +509,7 @@ private bool GallopMerge(TimChunk<T> left, TimChunk<T> right, ref int dest)
return true;
}

right.Wins = GallopLeft(right.Array, left.Array[left.Index], right.Index, right.Remaining, 0);
right.Wins = GallopingStrategy<T>.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);
Expand Down
Loading

0 comments on commit d634a74

Please sign in to comment.