Skip to content

Commit d634a74

Browse files
committed
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.
1 parent 9439ee2 commit d634a74

File tree

4 files changed

+241
-119
lines changed

4 files changed

+241
-119
lines changed

Algorithms.Tests/Sorters/Comparison/TimSorterTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@ namespace Algorithms.Tests.Sorters.Comparison;
99
public static class TimSorterTests
1010
{
1111
private static readonly IntComparer IntComparer = new();
12+
private static readonly TimSorterSettings Settings = new();
1213

1314
[Test]
1415
public static void ArraySorted(
1516
[Random(0, 10_000, 2000)] int n)
1617
{
1718
// Arrange
18-
var sorter = new TimSorter<int>(new TimSorterSettings());
19+
var sorter = new TimSorter<int>(Settings, IntComparer);
1920
var (correctArray, testArray) = RandomHelper.GetArrays(n);
2021

2122
// Act
22-
sorter.Sort(testArray, IntComparer);
23+
sorter.Sort(testArray);
2324
Array.Sort(correctArray, IntComparer);
2425

2526
// Assert
@@ -30,12 +31,12 @@ public static void ArraySorted(
3031
public static void TinyArray()
3132
{
3233
// Arrange
33-
var sorter = new TimSorter<int>(new TimSorterSettings());
34+
var sorter = new TimSorter<int>(Settings, IntComparer);
3435
var tinyArray = new[] { 1 };
3536
var correctArray = new[] { 1 };
3637

3738
// Act
38-
sorter.Sort(tinyArray, IntComparer);
39+
sorter.Sort(tinyArray);
3940

4041
// Assert
4142
Assert.That(correctArray, Is.EqualTo(tinyArray));
@@ -45,7 +46,7 @@ public static void TinyArray()
4546
public static void SmallChunks()
4647
{
4748
// Arrange
48-
var sorter = new TimSorter<int>(new TimSorterSettings());
49+
var sorter = new TimSorter<int>(Settings, IntComparer);
4950
var (correctArray, testArray) = RandomHelper.GetArrays(800);
5051
Array.Sort(correctArray, IntComparer);
5152
Array.Sort(testArray, IntComparer);
@@ -59,7 +60,7 @@ public static void SmallChunks()
5960
testArray[800 - 1] = min;
6061

6162
// Act
62-
sorter.Sort(testArray, IntComparer);
63+
sorter.Sort(testArray);
6364
Array.Sort(correctArray, IntComparer);
6465

6566
// Assert
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using Algorithms.Sorters.Utils;
2+
using NUnit.Framework;
3+
using System.Collections.Generic;
4+
5+
namespace Algorithms.Tests.Sorters.Utils
6+
{
7+
[TestFixture]
8+
public class GallopingStrategyTests
9+
{
10+
private readonly IComparer<int> comparer = Comparer<int>.Default;
11+
12+
[Test]
13+
public void GallopLeft_KeyPresent_ReturnsCorrectIndex()
14+
{
15+
var array = new[] { 1, 2, 3, 4, 5 };
16+
var index = GallopingStrategy<int>.GallopLeft(array, 3, 0, array.Length, comparer);
17+
Assert.That(index, Is.EqualTo(2));
18+
}
19+
20+
[Test]
21+
public void GallopLeft_KeyNotPresent_ReturnsCorrectIndex()
22+
{
23+
var array = new[] { 1, 2, 4, 5 };
24+
var index = GallopingStrategy<int>.GallopLeft(array, 3, 0, array.Length, comparer);
25+
Assert.That(index, Is.EqualTo(2));
26+
}
27+
28+
[Test]
29+
public void GallopLeft_KeyLessThanAll_ReturnsZero()
30+
{
31+
var array = new[] { 2, 3, 4, 5 };
32+
var index = GallopingStrategy<int>.GallopLeft(array, 1, 0, array.Length, comparer);
33+
Assert.That(index, Is.EqualTo(0));
34+
}
35+
36+
[Test]
37+
public void GallopLeft_KeyGreaterThanAll_ReturnsLength()
38+
{
39+
var array = new[] { 1, 2, 3, 4 };
40+
var index = GallopingStrategy<int>.GallopLeft(array, 5, 0, array.Length, comparer);
41+
Assert.That(index, Is.EqualTo(array.Length));
42+
}
43+
44+
[Test]
45+
public void GallopRight_KeyPresent_ReturnsCorrectIndex()
46+
{
47+
var array = new[] { 1, 2, 3, 4, 5 };
48+
var index = GallopingStrategy<int>.GallopRight(array, 3, 0, array.Length, comparer);
49+
Assert.That(index, Is.EqualTo(3));
50+
}
51+
52+
[Test]
53+
public void GallopRight_KeyNotPresent_ReturnsCorrectIndex()
54+
{
55+
var array = new[] { 1, 2, 4, 5 };
56+
var index = GallopingStrategy<int>.GallopRight(array, 3, 0, array.Length, comparer);
57+
Assert.That(index, Is.EqualTo(2));
58+
}
59+
60+
[Test]
61+
public void GallopRight_KeyLessThanAll_ReturnsZero()
62+
{
63+
var array = new[] { 2, 3, 4, 5 };
64+
var index = GallopingStrategy<int>.GallopRight(array, 1, 0, array.Length, comparer);
65+
Assert.That(index, Is.EqualTo(0));
66+
}
67+
68+
[Test]
69+
public void GallopRight_KeyGreaterThanAll_ReturnsLength()
70+
{
71+
var array = new[] { 1, 2, 3, 4 };
72+
var index = GallopingStrategy<int>.GallopRight(array, 5, 0, array.Length, comparer);
73+
Assert.That(index, Is.EqualTo(array.Length));
74+
}
75+
76+
[Test]
77+
public void GallopLeft_EmptyArray_ReturnsZero()
78+
{
79+
var array = new int[] { };
80+
var index = GallopingStrategy<int>.GallopLeft(array, 1, 0, array.Length, comparer);
81+
Assert.That(index, Is.EqualTo(0));
82+
}
83+
84+
[Test]
85+
public void GallopRight_EmptyArray_ReturnsZero()
86+
{
87+
var array = new int[] { };
88+
var index = GallopingStrategy<int>.GallopRight(array, 1, 0, array.Length, comparer);
89+
Assert.That(index, Is.EqualTo(0));
90+
}
91+
92+
// Test when (shiftable << 1) < 0 is true
93+
[Test]
94+
public void TestBoundLeftShift_WhenShiftableCausesNegativeShift_ReturnsShiftedValuePlusOne()
95+
{
96+
// Arrange
97+
int shiftable = int.MaxValue; // This should cause a negative result after left shift
98+
99+
// Act
100+
int result = GallopingStrategy<int>.BoundLeftShift(shiftable);
101+
102+
// Assert
103+
Assert.That((shiftable << 1) + 1, Is.EqualTo(result)); // True branch
104+
}
105+
106+
// Test when (shiftable << 1) < 0 is false
107+
[Test]
108+
public void TestBoundLeftShift_WhenShiftableDoesNotCauseNegativeShift_ReturnsMaxValue()
109+
{
110+
// Arrange
111+
int shiftable = 1; // This will not cause a negative result after left shift
112+
113+
// Act
114+
int result = GallopingStrategy<int>.BoundLeftShift(shiftable);
115+
116+
// Assert
117+
Assert.That(int.MaxValue, Is.EqualTo(result)); // False branch
118+
}
119+
}
120+
}

Algorithms/Sorters/Comparison/TimSorter.cs

Lines changed: 8 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using Algorithms.Sorters.Utils;
34

45
namespace Algorithms.Sorters.Comparison;
56

@@ -54,7 +55,7 @@ private class TimChunk<Tc>
5455
public int Wins { get; set; }
5556
}
5657

57-
public TimSorter(TimSorterSettings settings)
58+
public TimSorter(TimSorterSettings settings, IComparer<T> comparer)
5859
{
5960
initMinGallop = minGallop;
6061
runBase = new int[85];
@@ -64,6 +65,8 @@ public TimSorter(TimSorterSettings settings)
6465

6566
minGallop = settings.MinGallop;
6667
minMerge = settings.MinMerge;
68+
69+
this.comparer = comparer ?? Comparer<T>.Default;
6770
}
6871

6972
/// <summary>
@@ -163,15 +166,6 @@ private static void ReverseRange(T[] array, int start, int end)
163166
}
164167
}
165168

166-
/// <summary>
167-
/// Left shift a value, preventing a roll over to negative numbers.
168-
/// </summary>
169-
/// <param name="shiftable">int value to left shift.</param>
170-
/// <returns>Left shifted value, bound to 2,147,483,647.</returns>
171-
private static int BoundLeftShift(int shiftable) => (shiftable << 1) < 0
172-
? (shiftable << 1) + 1
173-
: int.MaxValue;
174-
175169
/// <summary>
176170
/// Check the chunks before getting in to a merge to make sure there's something to actually do.
177171
/// </summary>
@@ -270,105 +264,6 @@ private int CountRunAndMakeAscending(T[] array, int start)
270264
return runHi - start;
271265
}
272266

273-
/// <summary>
274-
/// Find the position in the array that a key should fit to the left of where it currently sits.
275-
/// </summary>
276-
/// <param name="array">Array to search.</param>
277-
/// <param name="key">Key to place in the array.</param>
278-
/// <param name="i">Base index for the key.</param>
279-
/// <param name="len">Length of the chunk to run through.</param>
280-
/// <param name="hint">Initial starting position to start from.</param>
281-
/// <returns>Offset for the key's location.</returns>
282-
private int GallopLeft(T[] array, T key, int i, int len, int hint)
283-
{
284-
var (offset, lastOfs) = comparer.Compare(key, array[i + hint]) > 0
285-
? RightRun(array, key, i, len, hint, 0)
286-
: LeftRun(array, key, i, hint, 1);
287-
288-
return FinalOffset(array, key, i, offset, lastOfs, 1);
289-
}
290-
291-
/// <summary>
292-
/// Find the position in the array that a key should fit to the right of where it currently sits.
293-
/// </summary>
294-
/// <param name="array">Array to search.</param>
295-
/// <param name="key">Key to place in the array.</param>
296-
/// <param name="i">Base index for the key.</param>
297-
/// <param name="len">Length of the chunk to run through.</param>
298-
/// <param name="hint">Initial starting position to start from.</param>
299-
/// <returns>Offset for the key's location.</returns>
300-
private int GallopRight(T[] array, T key, int i, int len, int hint)
301-
{
302-
var (offset, lastOfs) = comparer.Compare(key, array[i + hint]) < 0
303-
? LeftRun(array, key, i, hint, 0)
304-
: RightRun(array, key, i, len, hint, -1);
305-
306-
return FinalOffset(array, key, i, offset, lastOfs, 0);
307-
}
308-
309-
private (int offset, int lastOfs) LeftRun(T[] array, T key, int i, int hint, int lt)
310-
{
311-
var maxOfs = hint + 1;
312-
var (offset, tmp) = (1, 0);
313-
314-
while (offset < maxOfs && comparer.Compare(key, array[i + hint - offset]) < lt)
315-
{
316-
tmp = offset;
317-
offset = BoundLeftShift(offset);
318-
}
319-
320-
if (offset > maxOfs)
321-
{
322-
offset = maxOfs;
323-
}
324-
325-
var lastOfs = hint - offset;
326-
offset = hint - tmp;
327-
328-
return (offset, lastOfs);
329-
}
330-
331-
private (int offset, int lastOfs) RightRun(T[] array, T key, int i, int len, int hint, int gt)
332-
{
333-
var (offset, lastOfs) = (1, 0);
334-
var maxOfs = len - hint;
335-
while (offset < maxOfs && comparer.Compare(key, array[i + hint + offset]) > gt)
336-
{
337-
lastOfs = offset;
338-
offset = BoundLeftShift(offset);
339-
}
340-
341-
if (offset > maxOfs)
342-
{
343-
offset = maxOfs;
344-
}
345-
346-
offset += hint;
347-
lastOfs += hint;
348-
349-
return (offset, lastOfs);
350-
}
351-
352-
private int FinalOffset(T[] array, T key, int i, int offset, int lastOfs, int lt)
353-
{
354-
lastOfs++;
355-
while (lastOfs < offset)
356-
{
357-
var m = lastOfs + (int)((uint)(offset - lastOfs) >> 1);
358-
359-
if (comparer.Compare(key, array[i + m]) < lt)
360-
{
361-
offset = m;
362-
}
363-
else
364-
{
365-
lastOfs = m + 1;
366-
}
367-
}
368-
369-
return offset;
370-
}
371-
372267
/// <summary>
373268
/// Sorts the specified portion of the specified array using a binary
374269
/// 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)
470365

471366
stackSize--;
472367

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

475370
baseA += k;
476371
lenA -= k;
@@ -480,7 +375,7 @@ private void MergeAt(T[] array, int index)
480375
return;
481376
}
482377

483-
lenB = GallopLeft(array, array[baseA + lenA - 1], baseB, lenB, lenB - 1);
378+
lenB = GallopingStrategy<T>.GallopLeft(array, array[baseA + lenA - 1], baseB, lenB, comparer);
484379

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

596491
private bool GallopMerge(TimChunk<T> left, TimChunk<T> right, ref int dest)
597492
{
598-
left.Wins = GallopRight(left.Array, right.Array[right.Index], left.Index, left.Remaining, 0);
493+
left.Wins = GallopingStrategy<T>.GallopRight(left.Array, right.Array[right.Index], left.Index, left.Remaining, comparer);
599494
if (left.Wins != 0)
600495
{
601496
Array.Copy(left.Array, left.Index, right.Array, dest, left.Wins);
@@ -614,7 +509,7 @@ private bool GallopMerge(TimChunk<T> left, TimChunk<T> right, ref int dest)
614509
return true;
615510
}
616511

617-
right.Wins = GallopLeft(right.Array, left.Array[left.Index], right.Index, right.Remaining, 0);
512+
right.Wins = GallopingStrategy<T>.GallopLeft(right.Array, left.Array[left.Index], right.Index, right.Remaining, comparer);
618513
if (right.Wins != 0)
619514
{
620515
Array.Copy(right.Array, right.Index, right.Array, dest, right.Wins);

0 commit comments

Comments
 (0)