Skip to content

Commit 69b49f7

Browse files
williamfisetclaude
andcommitted
Add Tim sort implementation with tests
Hybrid merge sort + insertion sort: splits array into runs of 32, sorts each with insertion sort, then iteratively merges. Stable, O(n log n) worst case, O(n) best case. Implements InplaceSort and is integrated into the shared SortingTest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d57da50 commit 69b49f7

File tree

6 files changed

+215
-2
lines changed

6 files changed

+215
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch
283283
- [Quicksort (in-place, Hoare partitioning)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java) **- Θ(nlog(n))**
284284
- [Quicksort3 (Dutch National Flag algorithm)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java) **- Θ(nlog(n))**
285285
- [Selection sort](src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java) **- O(n<sup>2</sup>)**
286+
- [Tim sort](src/main/java/com/williamfiset/algorithms/sorting/TimSort.java) **- O(nlog(n))**
286287
- [Radix sort](src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java) **- O(n\*w)**
287288

288289
# String algorithms

src/main/java/com/williamfiset/algorithms/sorting/BUILD

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ java_binary(
7777
runtime_deps = [":sorting"],
7878
)
7979

80+
# bazel run //src/main/java/com/williamfiset/algorithms/sorting:TimSort
81+
java_binary(
82+
name = "TimSort",
83+
main_class = "com.williamfiset.algorithms.sorting.TimSort",
84+
runtime_deps = [":sorting"],
85+
)
86+
8087
# bazel run //src/main/java/com/williamfiset/algorithms/sorting:SelectionSort
8188
java_binary(
8289
name = "SelectionSort",
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* Tim sort implementation — a hybrid sorting algorithm combining merge sort and insertion sort.
3+
*
4+
* Tim sort divides the array into small chunks called "runs" and sorts each run using insertion
5+
* sort (which is efficient for small or nearly-sorted data). It then merges the runs using a
6+
* merge step similar to merge sort. This is the algorithm used by Java's Arrays.sort() for objects
7+
* and Python's built-in sort.
8+
*
9+
* Time Complexity: O(n log n) worst case, O(n) best case (already sorted)
10+
* Space Complexity: O(n) for the merge buffer
11+
*
12+
* @author Claude
13+
*/
14+
package com.williamfiset.algorithms.sorting;
15+
16+
public class TimSort implements InplaceSort {
17+
18+
private static final int MIN_RUN = 32;
19+
20+
@Override
21+
public void sort(int[] values) {
22+
timSort(values);
23+
}
24+
25+
public static void timSort(int[] ar) {
26+
if (ar == null || ar.length <= 1)
27+
return;
28+
29+
int n = ar.length;
30+
31+
// Sort individual runs using insertion sort
32+
for (int i = 0; i < n; i += MIN_RUN)
33+
insertionSort(ar, i, Math.min(i + MIN_RUN - 1, n - 1));
34+
35+
// Merge runs, doubling the merge size each iteration
36+
for (int size = MIN_RUN; size < n; size *= 2) {
37+
for (int left = 0; left < n; left += 2 * size) {
38+
int mid = Math.min(left + size - 1, n - 1);
39+
int right = Math.min(left + 2 * size - 1, n - 1);
40+
if (mid < right)
41+
merge(ar, left, mid, right);
42+
}
43+
}
44+
}
45+
46+
/** Insertion sort on ar[lo..hi] inclusive. */
47+
private static void insertionSort(int[] ar, int lo, int hi) {
48+
for (int i = lo + 1; i <= hi; i++) {
49+
int key = ar[i];
50+
int j = i - 1;
51+
while (j >= lo && ar[j] > key) {
52+
ar[j + 1] = ar[j];
53+
j--;
54+
}
55+
ar[j + 1] = key;
56+
}
57+
}
58+
59+
/** Merges two sorted sub-arrays ar[lo..mid] and ar[mid+1..hi]. */
60+
private static void merge(int[] ar, int lo, int mid, int hi) {
61+
int len1 = mid - lo + 1;
62+
int len2 = hi - mid;
63+
64+
int[] left = new int[len1];
65+
int[] right = new int[len2];
66+
System.arraycopy(ar, lo, left, 0, len1);
67+
System.arraycopy(ar, mid + 1, right, 0, len2);
68+
69+
int i = 0, j = 0, k = lo;
70+
while (i < len1 && j < len2) {
71+
// Use <= to maintain stability: equal elements from the left run come first
72+
if (left[i] <= right[j])
73+
ar[k++] = left[i++];
74+
else
75+
ar[k++] = right[j++];
76+
}
77+
while (i < len1)
78+
ar[k++] = left[i++];
79+
while (j < len2)
80+
ar[k++] = right[j++];
81+
}
82+
83+
public static void main(String[] args) {
84+
int[] array = {10, 4, 6, 4, 8, -13, 2, 3};
85+
timSort(array);
86+
// Prints:
87+
// [-13, 2, 3, 4, 4, 6, 8, 10]
88+
System.out.println(java.util.Arrays.toString(array));
89+
}
90+
}

src/test/java/com/williamfiset/algorithms/sorting/BUILD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ java_test(
127127
deps = TEST_DEPS,
128128
)
129129

130+
java_test(
131+
name = "TimSortTest",
132+
srcs = ["TimSortTest.java"],
133+
main_class = "org.junit.platform.console.ConsoleLauncher",
134+
use_testrunner = False,
135+
args = ["--select-class=com.williamfiset.algorithms.sorting.TimSortTest"],
136+
runtime_deps = JUNIT5_RUNTIME_DEPS,
137+
deps = TEST_DEPS,
138+
)
139+
130140
java_test(
131141
name = "SortingTest",
132142
srcs = ["SortingTest.java"],

src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ enum SortingAlgorithm {
2525
QUICK_SORT(new QuickSort()),
2626
QUICK_SORT3(new QuickSort3()),
2727
RADIX_SORT(new RadixSort()),
28-
SELECTION_SORT(new SelectionSort());
28+
SELECTION_SORT(new SelectionSort()),
29+
TIM_SORT(new TimSort());
2930

3031
private InplaceSort algorithm;
3132

@@ -49,7 +50,8 @@ public InplaceSort getSortingAlgorithm() {
4950
SortingAlgorithm.QUICK_SORT,
5051
SortingAlgorithm.QUICK_SORT3,
5152
SortingAlgorithm.RADIX_SORT,
52-
SortingAlgorithm.SELECTION_SORT);
53+
SortingAlgorithm.SELECTION_SORT,
54+
SortingAlgorithm.TIM_SORT);
5355

5456
@Test
5557
public void verifySortingAlgorithms_smallPositiveIntegersOnly() {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.williamfiset.algorithms.sorting;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import com.williamfiset.algorithms.utils.TestUtils;
6+
import java.util.Arrays;
7+
import org.junit.jupiter.api.*;
8+
9+
public class TimSortTest {
10+
11+
private final TimSort sorter = new TimSort();
12+
13+
@Test
14+
public void testEmptyArray() {
15+
int[] array = {};
16+
sorter.sort(array);
17+
assertThat(array).isEqualTo(new int[] {});
18+
}
19+
20+
@Test
21+
public void testSingleElement() {
22+
int[] array = {42};
23+
sorter.sort(array);
24+
assertThat(array).isEqualTo(new int[] {42});
25+
}
26+
27+
@Test
28+
public void testAlreadySorted() {
29+
int[] array = {1, 2, 3, 4, 5};
30+
sorter.sort(array);
31+
assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5});
32+
}
33+
34+
@Test
35+
public void testReverseSorted() {
36+
int[] array = {5, 4, 3, 2, 1};
37+
sorter.sort(array);
38+
assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5});
39+
}
40+
41+
@Test
42+
public void testWithDuplicates() {
43+
int[] array = {10, 4, 6, 4, 8, -13, 2, 3};
44+
sorter.sort(array);
45+
assertThat(array).isEqualTo(new int[] {-13, 2, 3, 4, 4, 6, 8, 10});
46+
}
47+
48+
@Test
49+
public void testAllSameElements() {
50+
int[] array = {3, 3, 3, 3, 3};
51+
sorter.sort(array);
52+
assertThat(array).isEqualTo(new int[] {3, 3, 3, 3, 3});
53+
}
54+
55+
@Test
56+
public void testNegativeNumbers() {
57+
int[] array = {-3, -1, -4, -1, -5};
58+
sorter.sort(array);
59+
assertThat(array).isEqualTo(new int[] {-5, -4, -3, -1, -1});
60+
}
61+
62+
@Test
63+
public void testMixedPositiveAndNegative() {
64+
int[] array = {3, -2, 0, 7, -5, 1};
65+
sorter.sort(array);
66+
assertThat(array).isEqualTo(new int[] {-5, -2, 0, 1, 3, 7});
67+
}
68+
69+
@Test
70+
public void testTwoElements() {
71+
int[] array = {9, 1};
72+
sorter.sort(array);
73+
assertThat(array).isEqualTo(new int[] {1, 9});
74+
}
75+
76+
@Test
77+
public void testSmallerThanMinRun() {
78+
int[] array = {5, 3, 8, 1, 9, 2, 7};
79+
sorter.sort(array);
80+
assertThat(array).isEqualTo(new int[] {1, 2, 3, 5, 7, 8, 9});
81+
}
82+
83+
@Test
84+
public void testLargerThanMinRun() {
85+
// 100 elements to exercise the merge phase
86+
int[] array = TestUtils.randomIntegerArray(100, -50, 51);
87+
int[] expected = array.clone();
88+
Arrays.sort(expected);
89+
sorter.sort(array);
90+
assertThat(array).isEqualTo(expected);
91+
}
92+
93+
@Test
94+
public void testRandomized() {
95+
for (int size = 0; size < 500; size++) {
96+
int[] values = TestUtils.randomIntegerArray(size, -50, 51);
97+
int[] expected = values.clone();
98+
Arrays.sort(expected);
99+
sorter.sort(values);
100+
assertThat(values).isEqualTo(expected);
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)