diff --git a/README.md b/README.md index 83f48ee64..72679b39a 100644 --- a/README.md +++ b/README.md @@ -258,13 +258,12 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [Bit manipulations](src/main/java/com/williamfiset/algorithms/other/BitManipulations.java) **- O(1)** - [List permutations](src/main/java/com/williamfiset/algorithms/other/Permutations.java) **- O(n!)** -- [:movie_camera:](https://www.youtube.com/watch?v=RnlHPR0lyOE) [Power set (set of all subsets)](src/main/java/com/williamfiset/algorithms/other/PowerSet.java) **- O(2n)** +- [:movie_camera:](https://www.youtube.com/watch?v=RnlHPR0lyOE) [Power set (set of all subsets)](src/main/java/com/williamfiset/algorithms/other/PowerSet.java) **- O(n · 2n)** - [Set combinations](src/main/java/com/williamfiset/algorithms/other/Combinations.java) **- O(n choose r)** - [Set combinations with repetition](src/main/java/com/williamfiset/algorithms/other/CombinationsWithRepetition.java) **- O((n+r-1) choose r)** - [Sliding Window Minimum/Maximum](src/main/java/com/williamfiset/algorithms/other/SlidingWindowMaximum.java) **- O(1)** - [Square Root Decomposition](src/main/java/com/williamfiset/algorithms/other/SquareRootDecomposition.java) **- O(1) point updates, O(√n) range queries** - [Unique set combinations](src/main/java/com/williamfiset/algorithms/other/UniqueCombinations.java) **- O(n choose r)** -- [Lazy Range Adder](src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java) **- O(1) range updates, O(n) to finalize all updates** # Search algorithms @@ -284,6 +283,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [Quicksort (in-place, Hoare partitioning)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java) **- Θ(nlog(n))** - [Quicksort3 (Dutch National Flag algorithm)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java) **- Θ(nlog(n))** - [Selection sort](src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java) **- O(n2)** +- [Tim sort](src/main/java/com/williamfiset/algorithms/sorting/TimSort.java) **- O(nlog(n))** - [Radix sort](src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java) **- O(n\*w)** # String algorithms diff --git a/src/main/java/com/williamfiset/algorithms/other/BUILD b/src/main/java/com/williamfiset/algorithms/other/BUILD index 77c05d3da..3a7586729 100644 --- a/src/main/java/com/williamfiset/algorithms/other/BUILD +++ b/src/main/java/com/williamfiset/algorithms/other/BUILD @@ -21,13 +21,6 @@ java_binary( runtime_deps = [":other"], ) -# bazel run //src/main/java/com/williamfiset/algorithms/other:LazyRangeAdder -java_binary( - name = "LazyRangeAdder", - main_class = "com.williamfiset.algorithms.other.LazyRangeAdder", - runtime_deps = [":other"], -) - # bazel run //src/main/java/com/williamfiset/algorithms/other:Permutations java_binary( name = "Permutations", diff --git a/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java b/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java deleted file mode 100644 index 33eaacfab..000000000 --- a/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * The LazyRangerAdder is a handy class for performing addition range updates of constant values on - * an array. This range adder is especially useful for offline algorithms which know all range - * updates ahead of time. - * - *

Time complexity to update O(1) but time complexity to finalize all additions is O(n) - * - * @author Atharva Thorve, aaathorve@gmail.com - */ -package com.williamfiset.algorithms.other; - -public class LazyRangeAdder { - - // The number of elements in the input array. - private int n; - - // The original input array - private int[] array; - - // The difference array with the deltas between values, size n+1 - private int[] differenceArray; - - // Initialize an instance of a LazyRangeAdder on some input values - public LazyRangeAdder(int[] array) { - this.array = array; - this.n = array.length; - - differenceArray = new int[n + 1]; - differenceArray[0] = array[0]; - for (int i = 1; i < n; i++) { - differenceArray[i] = array[i] - array[i - 1]; - } - } - - // Add `x` to the range [l, r] inclusive - public void add(int l, int r, int x) { - differenceArray[l] += x; - differenceArray[r + 1] -= x; - } - - // IMPORTANT: Make certain to call this method once all the additions - // have been made with add(l, r, x) - public void done() { - for (int i = 0; i < n; i++) { - if (i == 0) { - array[i] = differenceArray[i]; - } else { - array[i] = differenceArray[i] + array[i - 1]; - } - } - } - - public static void main(String[] args) { - // Array to be updated - int[] array = {10, 4, 6, 13, 8, 15, 17, 22}; - LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(array); - - // After below add(l, r, x), the - // elements should become [10, 14, 16, 23, 18, 15, 17, 22] - lazyRangeAdder.add(1, 4, 10); - lazyRangeAdder.done(); - System.out.println(java.util.Arrays.toString(array)); - - // After below add(l, r, x), the - // elements should become [22, 26, 28, 30, 25, 22, 24, 34] - lazyRangeAdder.add(3, 6, -5); - lazyRangeAdder.add(0, 7, 12); - lazyRangeAdder.done(); - System.out.println(java.util.Arrays.toString(array)); - } -} diff --git a/src/main/java/com/williamfiset/algorithms/other/PowerSet.java b/src/main/java/com/williamfiset/algorithms/other/PowerSet.java index 5ed95c8b5..ff8e6b7f0 100644 --- a/src/main/java/com/williamfiset/algorithms/other/PowerSet.java +++ b/src/main/java/com/williamfiset/algorithms/other/PowerSet.java @@ -1,84 +1,75 @@ /** - * This code snippet shows how to generate the powerset of a set which is the set of all subsets of - * a set. There are two common ways of doing this which are to use the binary representation of - * numbers on a computer or to do it recursively. Both methods are shown here, pick your flavor! + * Generates the power set of a set, which is the set of all subsets. * - *

Time Complexity: O( 2^n ) + * Two approaches are provided: an iterative method using binary representation of numbers, and a + * recursive backtracking method. Both produce the same result. + * + * Time Complexity: O(n * 2^n) * * @author William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.other; -public class PowerSet { - - // Use the fact that numbers represented in binary can be - // used to generate all the subsets of an array - static void powerSetUsingBinary(int[] set) { +import java.util.*; - final int N = set.length; - final int MAX_VAL = 1 << N; +public class PowerSet { - for (int subset = 0; subset < MAX_VAL; subset++) { - System.out.print("{ "); - for (int i = 0; i < N; i++) { - int mask = 1 << i; - if ((subset & mask) == mask) System.out.print(set[i] + " "); + /** + * Generates the power set using binary representation. Each integer from 0 to 2^n - 1 represents + * a subset, where bit i indicates whether element i is included. + */ + public static List> powerSetBinary(List set) { + int n = set.size(); + if (n > 30) + throw new IllegalArgumentException("Set too large: n=" + n + " (max 30)"); + int total = 1 << n; + List> result = new ArrayList<>(total); + + for (int mask = 0; mask < total; mask++) { + List subset = new ArrayList<>(); + for (int i = 0; i < n; i++) { + if ((mask & (1 << i)) != 0) + subset.add(set.get(i)); } - System.out.println("}"); + result.add(subset); } + return result; } - // Recursively generate the powerset (set of all subsets) of an array by maintaining - // a boolean array used to indicate which element have been selected - static void powerSetRecursive(int at, int[] set, boolean[] used) { - - if (at == set.length) { - - // Print found subset! - System.out.print("{ "); - for (int i = 0; i < set.length; i++) if (used[i]) System.out.print(set[i] + " "); - System.out.println("}"); - - } else { - - // Include this element - used[at] = true; - powerSetRecursive(at + 1, set, used); + /** + * Generates the power set using recursive backtracking. At each element, branches into including + * or excluding it. + */ + public static List> powerSetRecursive(List set) { + List> result = new ArrayList<>(); + recurse(0, set, new ArrayList<>(), result); + return result; + } - // Backtrack and don't include this element - used[at] = false; - powerSetRecursive(at + 1, set, used); + private static void recurse(int at, List set, List current, List> result) { + if (at == set.size()) { + // Snapshot the current subset — must copy since 'current' is mutated during backtracking + result.add(new ArrayList<>(current)); + return; } + // Include set[at] and explore all subsets of the remaining elements + current.add(set.get(at)); + recurse(at + 1, set, current, result); + + // Backtrack: undo the inclusion of set[at], then explore without it + current.remove(current.size() - 1); + recurse(at + 1, set, current, result); } public static void main(String[] args) { + List set = List.of(1, 2, 3); - // Example usage: - int[] set = {1, 2, 3}; - - powerSetUsingBinary(set); - // prints: - // { } - // { 1 } - // { 2 } - // { 1 2 } - // { 3 } - // { 1 3 } - // { 2 3 } - // { 1 2 3 } - - System.out.println(); - - powerSetRecursive(0, set, new boolean[set.length]); - // prints: - // { 1 2 3 } - // { 1 2 } - // { 1 3 } - // { 1 } - // { 2 3 } - // { 2 } - // { 3 } - // { } + System.out.println("Binary method:"); + for (List subset : powerSetBinary(set)) + System.out.println(subset); + System.out.println("\nRecursive method:"); + for (List subset : powerSetRecursive(set)) + System.out.println(subset); } } diff --git a/src/main/java/com/williamfiset/algorithms/sorting/BUILD b/src/main/java/com/williamfiset/algorithms/sorting/BUILD index 1c3df657c..91238898b 100644 --- a/src/main/java/com/williamfiset/algorithms/sorting/BUILD +++ b/src/main/java/com/williamfiset/algorithms/sorting/BUILD @@ -77,6 +77,13 @@ java_binary( runtime_deps = [":sorting"], ) +# bazel run //src/main/java/com/williamfiset/algorithms/sorting:TimSort +java_binary( + name = "TimSort", + main_class = "com.williamfiset.algorithms.sorting.TimSort", + runtime_deps = [":sorting"], +) + # bazel run //src/main/java/com/williamfiset/algorithms/sorting:SelectionSort java_binary( name = "SelectionSort", diff --git a/src/main/java/com/williamfiset/algorithms/sorting/TimSort.java b/src/main/java/com/williamfiset/algorithms/sorting/TimSort.java new file mode 100644 index 000000000..b98fb5870 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/TimSort.java @@ -0,0 +1,90 @@ +/** + * Tim sort implementation — a hybrid sorting algorithm combining merge sort and insertion sort. + * + * Tim sort divides the array into small chunks called "runs" and sorts each run using insertion + * sort (which is efficient for small or nearly-sorted data). It then merges the runs using a + * merge step similar to merge sort. This is the algorithm used by Java's Arrays.sort() for objects + * and Python's built-in sort. + * + * Time Complexity: O(n log n) worst case, O(n) best case (already sorted) + * Space Complexity: O(n) for the merge buffer + * + * @author Claude + */ +package com.williamfiset.algorithms.sorting; + +public class TimSort implements InplaceSort { + + private static final int MIN_RUN = 32; + + @Override + public void sort(int[] values) { + timSort(values); + } + + public static void timSort(int[] ar) { + if (ar == null || ar.length <= 1) + return; + + int n = ar.length; + + // Sort individual runs using insertion sort + for (int i = 0; i < n; i += MIN_RUN) + insertionSort(ar, i, Math.min(i + MIN_RUN - 1, n - 1)); + + // Merge runs, doubling the merge size each iteration + for (int size = MIN_RUN; size < n; size *= 2) { + for (int left = 0; left < n; left += 2 * size) { + int mid = Math.min(left + size - 1, n - 1); + int right = Math.min(left + 2 * size - 1, n - 1); + if (mid < right) + merge(ar, left, mid, right); + } + } + } + + /** Insertion sort on ar[lo..hi] inclusive. */ + private static void insertionSort(int[] ar, int lo, int hi) { + for (int i = lo + 1; i <= hi; i++) { + int key = ar[i]; + int j = i - 1; + while (j >= lo && ar[j] > key) { + ar[j + 1] = ar[j]; + j--; + } + ar[j + 1] = key; + } + } + + /** Merges two sorted sub-arrays ar[lo..mid] and ar[mid+1..hi]. */ + private static void merge(int[] ar, int lo, int mid, int hi) { + int len1 = mid - lo + 1; + int len2 = hi - mid; + + int[] left = new int[len1]; + int[] right = new int[len2]; + System.arraycopy(ar, lo, left, 0, len1); + System.arraycopy(ar, mid + 1, right, 0, len2); + + int i = 0, j = 0, k = lo; + while (i < len1 && j < len2) { + // Use <= to maintain stability: equal elements from the left run come first + if (left[i] <= right[j]) + ar[k++] = left[i++]; + else + ar[k++] = right[j++]; + } + while (i < len1) + ar[k++] = left[i++]; + while (j < len2) + ar[k++] = right[j++]; + } + + public static void main(String[] args) { + int[] array = {10, 4, 6, 4, 8, -13, 2, 3}; + timSort(array); + // Prints: + // [-13, 2, 3, 4, 4, 6, 8, 10] + System.out.println(java.util.Arrays.toString(array)); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/other/BUILD b/src/test/java/com/williamfiset/algorithms/other/BUILD index f11afce4d..c41074f2a 100644 --- a/src/test/java/com/williamfiset/algorithms/other/BUILD +++ b/src/test/java/com/williamfiset/algorithms/other/BUILD @@ -18,31 +18,32 @@ TEST_DEPS = [ ] + JUNIT5_DEPS java_test( - name = "LazyRangeAdderTest", - srcs = ["LazyRangeAdderTest.java"], + name = "SlidingWindowMaximumTest", + srcs = ["SlidingWindowMaximumTest.java"], main_class = "org.junit.platform.console.ConsoleLauncher", use_testrunner = False, - args = ["--select-class=com.williamfiset.algorithms.other.LazyRangeAdderTest"], + args = ["--select-class=com.williamfiset.algorithms.other.SlidingWindowMaximumTest"], runtime_deps = JUNIT5_RUNTIME_DEPS, deps = TEST_DEPS, ) java_test( - name = "SlidingWindowMaximumTest", - srcs = ["SlidingWindowMaximumTest.java"], + name = "BitManipulationsTest", + srcs = ["BitManipulationsTest.java"], main_class = "org.junit.platform.console.ConsoleLauncher", use_testrunner = False, - args = ["--select-class=com.williamfiset.algorithms.other.SlidingWindowMaximumTest"], + args = ["--select-class=com.williamfiset.algorithms.other.BitManipulationsTest"], runtime_deps = JUNIT5_RUNTIME_DEPS, deps = TEST_DEPS, ) +# bazel test //src/test/java/com/williamfiset/algorithms/other:PowerSetTest java_test( - name = "BitManipulationsTest", - srcs = ["BitManipulationsTest.java"], + name = "PowerSetTest", + srcs = ["PowerSetTest.java"], main_class = "org.junit.platform.console.ConsoleLauncher", use_testrunner = False, - args = ["--select-class=com.williamfiset.algorithms.other.BitManipulationsTest"], + args = ["--select-class=com.williamfiset.algorithms.other.PowerSetTest"], runtime_deps = JUNIT5_RUNTIME_DEPS, deps = TEST_DEPS, ) diff --git a/src/test/java/com/williamfiset/algorithms/other/LazyRangeAdderTest.java b/src/test/java/com/williamfiset/algorithms/other/LazyRangeAdderTest.java deleted file mode 100644 index 357a7bbfd..000000000 --- a/src/test/java/com/williamfiset/algorithms/other/LazyRangeAdderTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.williamfiset.algorithms.other; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.Test; - -public class LazyRangeAdderTest { - - @Test - public void rangeUpdateTest1() { - int[] a = {10, 5, 20, 40}; - LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(a); - lazyRangeAdder.add(0, 1, 10); - lazyRangeAdder.add(1, 3, 20); - lazyRangeAdder.add(2, 2, 30); - lazyRangeAdder.done(); - int[] expected = {20, 35, 70, 60}; - assertThat(a).isEqualTo(expected); - } - - @Test - public void rangeUpdateTest2() { - int[] a = {270, 311, 427, 535, 334, 193, 174}; - LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(a); - lazyRangeAdder.add(2, 5, 32); - lazyRangeAdder.add(0, 4, 101); - lazyRangeAdder.add(5, 6, -73); - lazyRangeAdder.done(); - int[] expected = {371, 412, 560, 668, 467, 152, 101}; - assertThat(a).isEqualTo(expected); - } - - @Test - public void randomRangeAdditionTests() { - // Try several different array sizes - for (int n = 1; n < 1000; n++) { - - int[] arr1 = new int[n]; - randomFill(arr1); - int[] arr2 = arr1.clone(); - - LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(arr1); - - // Do 50 random range adds - for (int i = 0; i < 50; i++) { - // Generate a random range - int l = randValue(0, n); - int r = randValue(0, n); - l = Math.min(l, r); - r = Math.max(l, r); - - int x = randValue(-100, 100); - lazyRangeAdder.add(l, r, x); - slowRangeAdd(arr2, l, r, x); - } - - lazyRangeAdder.done(); - - assertThat(arr1).isEqualTo(arr2); - } - } - - // Adds `x` to the range [l, r] in arr - private static void slowRangeAdd(int[] arr, int l, int r, int x) { - for (int i = l; i <= r; i++) { - arr[i] += x; - } - } - - private static void randomFill(int[] arr) { - for (int i = 0; i < arr.length; i++) { - arr[i] = randValue(0, 1000); - } - } - - // Generates a random number between [min, max) - private static int randValue(int min, int max) { - return min + (int) (Math.random() * ((max - min))); - } -} diff --git a/src/test/java/com/williamfiset/algorithms/other/PowerSetTest.java b/src/test/java/com/williamfiset/algorithms/other/PowerSetTest.java new file mode 100644 index 000000000..e5243a9b1 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/other/PowerSetTest.java @@ -0,0 +1,88 @@ +package com.williamfiset.algorithms.other; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.*; +import java.util.stream.Collectors; +import org.junit.jupiter.api.*; + +public class PowerSetTest { + + /** Converts a power set to a set of sorted string representations for easy comparison. */ + private static > Set normalize(List> powerSet) { + return powerSet.stream() + .map(subset -> { + List sorted = new ArrayList<>(subset); + Collections.sort(sorted); + return sorted.toString(); + }) + .collect(Collectors.toSet()); + } + + @Test + public void emptySet_binary() { + List> result = PowerSet.powerSetBinary(List.of()); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEmpty(); + } + + @Test + public void emptySet_recursive() { + List> result = PowerSet.powerSetRecursive(List.of()); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isEmpty(); + } + + @Test + public void singleElement_binary() { + List> result = PowerSet.powerSetBinary(List.of(42)); + assertThat(normalize(result)).containsExactly("[]", "[42]"); + } + + @Test + public void singleElement_recursive() { + List> result = PowerSet.powerSetRecursive(List.of(42)); + assertThat(normalize(result)).containsExactly("[]", "[42]"); + } + + @Test + public void threeElements_binary() { + List> result = PowerSet.powerSetBinary(List.of(1, 2, 3)); + assertThat(result).hasSize(8); + assertThat(normalize(result)) + .containsExactly("[]", "[1]", "[2]", "[3]", "[1, 2]", "[1, 3]", "[2, 3]", "[1, 2, 3]"); + } + + @Test + public void threeElements_recursive() { + List> result = PowerSet.powerSetRecursive(List.of(1, 2, 3)); + assertThat(result).hasSize(8); + assertThat(normalize(result)) + .containsExactly("[]", "[1]", "[2]", "[3]", "[1, 2]", "[1, 3]", "[2, 3]", "[1, 2, 3]"); + } + + @Test + public void bothMethodsProduceSameSubsets() { + List set = List.of(5, 10, 15, 20); + Set binary = normalize(PowerSet.powerSetBinary(set)); + Set recursive = normalize(PowerSet.powerSetRecursive(set)); + assertThat(binary).isEqualTo(recursive); + } + + @Test + public void correctSize() { + for (int n = 0; n <= 5; n++) { + List set = new ArrayList<>(); + for (int i = 0; i < n; i++) + set.add(i); + assertThat(PowerSet.powerSetBinary(set)).hasSize(1 << n); + assertThat(PowerSet.powerSetRecursive(set)).hasSize(1 << n); + } + } + + @Test + public void worksWithStrings() { + List> result = PowerSet.powerSetBinary(List.of("a", "b")); + assertThat(normalize(result)).containsExactly("[]", "[a]", "[b]", "[a, b]"); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/sorting/BUILD b/src/test/java/com/williamfiset/algorithms/sorting/BUILD index 51be23a74..092e69fe2 100644 --- a/src/test/java/com/williamfiset/algorithms/sorting/BUILD +++ b/src/test/java/com/williamfiset/algorithms/sorting/BUILD @@ -127,6 +127,16 @@ java_test( deps = TEST_DEPS, ) +java_test( + name = "TimSortTest", + srcs = ["TimSortTest.java"], + main_class = "org.junit.platform.console.ConsoleLauncher", + use_testrunner = False, + args = ["--select-class=com.williamfiset.algorithms.sorting.TimSortTest"], + runtime_deps = JUNIT5_RUNTIME_DEPS, + deps = TEST_DEPS, +) + java_test( name = "SortingTest", srcs = ["SortingTest.java"], diff --git a/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java b/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java index 94a5765f6..3ff9b497f 100644 --- a/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java +++ b/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java @@ -25,7 +25,8 @@ enum SortingAlgorithm { QUICK_SORT(new QuickSort()), QUICK_SORT3(new QuickSort3()), RADIX_SORT(new RadixSort()), - SELECTION_SORT(new SelectionSort()); + SELECTION_SORT(new SelectionSort()), + TIM_SORT(new TimSort()); private InplaceSort algorithm; @@ -49,7 +50,8 @@ public InplaceSort getSortingAlgorithm() { SortingAlgorithm.QUICK_SORT, SortingAlgorithm.QUICK_SORT3, SortingAlgorithm.RADIX_SORT, - SortingAlgorithm.SELECTION_SORT); + SortingAlgorithm.SELECTION_SORT, + SortingAlgorithm.TIM_SORT); @Test public void verifySortingAlgorithms_smallPositiveIntegersOnly() { diff --git a/src/test/java/com/williamfiset/algorithms/sorting/TimSortTest.java b/src/test/java/com/williamfiset/algorithms/sorting/TimSortTest.java new file mode 100644 index 000000000..f31c56d51 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/sorting/TimSortTest.java @@ -0,0 +1,103 @@ +package com.williamfiset.algorithms.sorting; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import java.util.Arrays; +import org.junit.jupiter.api.*; + +public class TimSortTest { + + private final TimSort sorter = new TimSort(); + + @Test + public void testEmptyArray() { + int[] array = {}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {}); + } + + @Test + public void testSingleElement() { + int[] array = {42}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {42}); + } + + @Test + public void testAlreadySorted() { + int[] array = {1, 2, 3, 4, 5}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5}); + } + + @Test + public void testReverseSorted() { + int[] array = {5, 4, 3, 2, 1}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {1, 2, 3, 4, 5}); + } + + @Test + public void testWithDuplicates() { + int[] array = {10, 4, 6, 4, 8, -13, 2, 3}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {-13, 2, 3, 4, 4, 6, 8, 10}); + } + + @Test + public void testAllSameElements() { + int[] array = {3, 3, 3, 3, 3}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {3, 3, 3, 3, 3}); + } + + @Test + public void testNegativeNumbers() { + int[] array = {-3, -1, -4, -1, -5}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {-5, -4, -3, -1, -1}); + } + + @Test + public void testMixedPositiveAndNegative() { + int[] array = {3, -2, 0, 7, -5, 1}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {-5, -2, 0, 1, 3, 7}); + } + + @Test + public void testTwoElements() { + int[] array = {9, 1}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {1, 9}); + } + + @Test + public void testSmallerThanMinRun() { + int[] array = {5, 3, 8, 1, 9, 2, 7}; + sorter.sort(array); + assertThat(array).isEqualTo(new int[] {1, 2, 3, 5, 7, 8, 9}); + } + + @Test + public void testLargerThanMinRun() { + // 100 elements to exercise the merge phase + int[] array = TestUtils.randomIntegerArray(100, -50, 51); + int[] expected = array.clone(); + Arrays.sort(expected); + sorter.sort(array); + assertThat(array).isEqualTo(expected); + } + + @Test + public void testRandomized() { + for (int size = 0; size < 500; size++) { + int[] values = TestUtils.randomIntegerArray(size, -50, 51); + int[] expected = values.clone(); + Arrays.sort(expected); + sorter.sort(values); + assertThat(values).isEqualTo(expected); + } + } +}