From e6a1afee1fa286331e574c86126177124a05f66e Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 15:32:29 +0100 Subject: [PATCH 1/4] Add Sorter interface and jqwik property tests for the sorts - Sorter: void sort(int[] items); Insertion, Selection, Merge and Quick sort implement it and keep their range helpers private - One example test (SorterTest) and one jqwik property (every sorter agrees with Arrays.sort) run against all four implementations - Fix QuickSort partition scanning from index 0 instead of the range start: results were correct but every sort was O(n^2) (200k ints: 4354 ms -> 9 ms) Co-Authored-By: Claude Opus 5.5 --- pom.xml | 7 +++ .../algorithms/sorting/InsertionSort.java | 9 +++- .../algorithms/sorting/MergeSort.java | 9 +++- .../algorithms/sorting/QuickSort.java | 17 +++--- .../algorithms/sorting/SelectionSort.java | 9 +++- .../diegourban/algorithms/sorting/Sorter.java | 16 ++++++ .../problems/sorting/FindTheOddIntTest.java | 2 +- .../algorithms/sorting/InsertionSortTest.java | 19 ------- .../algorithms/sorting/MergeSortTest.java | 41 --------------- .../algorithms/sorting/QuickSortTest.java | 19 ------- .../algorithms/sorting/SelectionSortTest.java | 19 ------- .../sorting/SorterPropertyTest.java | 32 ++++++++++++ .../algorithms/sorting/SorterTest.java | 52 +++++++++++++++++++ 13 files changed, 140 insertions(+), 111 deletions(-) create mode 100644 src/main/java/io/github/diegourban/algorithms/sorting/Sorter.java delete mode 100644 src/test/java/io/github/diegourban/algorithms/sorting/InsertionSortTest.java delete mode 100644 src/test/java/io/github/diegourban/algorithms/sorting/MergeSortTest.java delete mode 100644 src/test/java/io/github/diegourban/algorithms/sorting/QuickSortTest.java delete mode 100644 src/test/java/io/github/diegourban/algorithms/sorting/SelectionSortTest.java create mode 100644 src/test/java/io/github/diegourban/algorithms/sorting/SorterPropertyTest.java create mode 100644 src/test/java/io/github/diegourban/algorithms/sorting/SorterTest.java diff --git a/pom.xml b/pom.xml index 68eae9d..afb2010 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 6.1.3 3.27.7 + 1.10.2 3.5.0 3.16.0 @@ -53,6 +54,12 @@ ${assertj.version} test + + net.jqwik + jqwik + ${jqwik.version} + test + diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java b/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java index ca36da6..122cbe6 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/InsertionSort.java @@ -3,7 +3,12 @@ /** * Insertion Sort Algorithm */ -public class InsertionSort { +public class InsertionSort implements Sorter { + + @Override + public void sort(final int[] items) { + sort(items, items.length); + } /** * Sort the items using insertion sort algorithm @@ -11,7 +16,7 @@ public class InsertionSort { * @param items items to sort * @param totalElements total elements */ - public void sort(final int[] items, final int totalElements) { + private void sort(final int[] items, final int totalElements) { for(int current = 1; current < totalElements; current++) { int analysis = current; diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/MergeSort.java b/src/main/java/io/github/diegourban/algorithms/sorting/MergeSort.java index b59825f..964d8b6 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/MergeSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/MergeSort.java @@ -3,7 +3,12 @@ /** * Merge sort algorithm */ -public class MergeSort { +public class MergeSort implements Sorter { + + @Override + public void sort(final int[] items) { + sort(items, 0, items.length); + } /** * Sorts the items from begin to end @@ -12,7 +17,7 @@ public class MergeSort { * @param begin begin * @param end end */ - public void sort(final int[] items, final int begin, final int end) { + private void sort(final int[] items, final int begin, final int end) { final int total = end - begin; if(total > 1) { final int middle = (begin + end) / 2; diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java b/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java index 1a73fb9..ed234d0 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/QuickSort.java @@ -3,7 +3,12 @@ /** * Quick sort algorithm */ -public class QuickSort { +public class QuickSort implements Sorter { + + @Override + public void sort(final int[] items) { + sort(items, 0, items.length); + } /** * Sort the items from start to end @@ -12,20 +17,20 @@ public class QuickSort { * @param start from * @param end end */ - public void sort(final int[] items, final int start, final int end) { + private void sort(final int[] items, final int start, final int end) { final int totalElements = end - start; if(totalElements > 1) { - final int pivotPosition = partition(items, end); + final int pivotPosition = partition(items, start, end); sort(items, start, pivotPosition); sort(items, pivotPosition + 1, end); } } - private int partition(final int[] items, final int end) { - int minorsFound = 0; + private int partition(final int[] items, final int start, final int end) { + int minorsFound = start; final int pivot = items[end - 1]; - for(int current = 0; current < end - 1; current++) { + for(int current = start; current < end - 1; current++) { final int currentItem = items[current]; if(currentItem <= pivot) { swap(items, current, minorsFound); diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java b/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java index 7a08916..98ca7b3 100644 --- a/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java +++ b/src/main/java/io/github/diegourban/algorithms/sorting/SelectionSort.java @@ -3,7 +3,12 @@ /** * Selection Sort algorithm */ -public class SelectionSort { +public class SelectionSort implements Sorter { + + @Override + public void sort(final int[] items) { + sort(items, items.length); + } /** * Sort the items using selection sort algorithm @@ -11,7 +16,7 @@ public class SelectionSort { * @param items items to sort * @param totalElements total elements */ - public void sort(final int[] items, final int totalElements) { + private void sort(final int[] items, final int totalElements) { for(int current = 0; current < totalElements - 1; current++) { final int smallerIndex = findSmallerIndex(items, current, totalElements); Integer currentItem = items[current]; diff --git a/src/main/java/io/github/diegourban/algorithms/sorting/Sorter.java b/src/main/java/io/github/diegourban/algorithms/sorting/Sorter.java new file mode 100644 index 0000000..e37bd79 --- /dev/null +++ b/src/main/java/io/github/diegourban/algorithms/sorting/Sorter.java @@ -0,0 +1,16 @@ +package io.github.diegourban.algorithms.sorting; + +/** + * An in-place, ascending sort of an {@code int[]}. + *

+ * Every sorting algorithm in this package implements it, so they can be swapped, compared and tested as one family. + */ +public interface Sorter { + + /** + * Sorts {@code items} in ascending order, in place. + * + * @param items the values to sort; an empty array is left as is + */ + void sort(int[] items); +} diff --git a/src/test/java/io/github/diegourban/algorithms/problems/sorting/FindTheOddIntTest.java b/src/test/java/io/github/diegourban/algorithms/problems/sorting/FindTheOddIntTest.java index 0974f44..62bb7ca 100644 --- a/src/test/java/io/github/diegourban/algorithms/problems/sorting/FindTheOddIntTest.java +++ b/src/test/java/io/github/diegourban/algorithms/problems/sorting/FindTheOddIntTest.java @@ -19,7 +19,7 @@ class FindTheOddIntTest { static int solve(int[] numbers) { var sorted = numbers.clone(); - new MergeSort().sort(sorted, 0, sorted.length); + new MergeSort().sort(sorted); var runStart = 0; for (var i = 1; i <= sorted.length; i++) { if (i == sorted.length || sorted[i] != sorted[runStart]) { diff --git a/src/test/java/io/github/diegourban/algorithms/sorting/InsertionSortTest.java b/src/test/java/io/github/diegourban/algorithms/sorting/InsertionSortTest.java deleted file mode 100644 index 9ea8d46..0000000 --- a/src/test/java/io/github/diegourban/algorithms/sorting/InsertionSortTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.diegourban.algorithms.sorting; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class InsertionSortTest { - - @Test - void test() { - final int[] unsortedItems = {5, 7, 2, 9, 4, 8, 1, 10, 3, 6}; - final int[] sortedItems = {1, 2, 3, 4, 5, 6 ,7, 8, 9, 10}; - - InsertionSort insertionSort = new InsertionSort(); - insertionSort.sort(unsortedItems, unsortedItems.length); - - assertThat(unsortedItems).isEqualTo(sortedItems); - } -} diff --git a/src/test/java/io/github/diegourban/algorithms/sorting/MergeSortTest.java b/src/test/java/io/github/diegourban/algorithms/sorting/MergeSortTest.java deleted file mode 100644 index fb10539..0000000 --- a/src/test/java/io/github/diegourban/algorithms/sorting/MergeSortTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.diegourban.algorithms.sorting; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class MergeSortTest { - - @Test - void shouldSortWithGroupsOfSameSize() { - final int[] unsortedItems = {4, 5, 1, 9, 2, 3, 6, 7}; - final int[] sortedItems = {1, 2, 3, 4, 5, 6, 7, 9}; - - MergeSort mergeSort = new MergeSort(); - mergeSort.sort(unsortedItems, 0, unsortedItems.length); - - assertThat(unsortedItems).isEqualTo(sortedItems); - } - - @Test - void shouldSortWithLeftoverAtGroup1() { - final int[] unsortedItems = {4, 1, 8, 9, 10, 2, 3, 7, 6}; - final int[] sortedItems = {1, 2, 3, 4, 6, 7, 8, 9, 10}; - - MergeSort mergeSort = new MergeSort(); - mergeSort.sort(unsortedItems, 0, unsortedItems.length); - - assertThat(unsortedItems).isEqualTo(sortedItems); - } - - @Test - void shouldSortWithLeftoverAtGroup2() { - final int[] unsortedItems = {4, 5, 2, 9, 1, 7, 6, 3, 10}; - final int[] sortedItems = {1, 2, 3, 4, 5, 6, 7, 9, 10}; - - MergeSort mergeSort = new MergeSort(); - mergeSort.sort(unsortedItems, 0, unsortedItems.length); - - assertThat(unsortedItems).isEqualTo(sortedItems); - } -} diff --git a/src/test/java/io/github/diegourban/algorithms/sorting/QuickSortTest.java b/src/test/java/io/github/diegourban/algorithms/sorting/QuickSortTest.java deleted file mode 100644 index b54938c..0000000 --- a/src/test/java/io/github/diegourban/algorithms/sorting/QuickSortTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.diegourban.algorithms.sorting; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class QuickSortTest { - - @Test - void shouldSort() { - final int[] unsortedItems = {4, 5, 1, 9, 2, 3, 6, 7}; - final int[] sortedItems = {1, 2, 3, 4, 5, 6, 7, 9}; - - QuickSort quickSort = new QuickSort(); - quickSort.sort(unsortedItems, 0, unsortedItems.length); - - assertThat(unsortedItems).isEqualTo(sortedItems); - } -} diff --git a/src/test/java/io/github/diegourban/algorithms/sorting/SelectionSortTest.java b/src/test/java/io/github/diegourban/algorithms/sorting/SelectionSortTest.java deleted file mode 100644 index 2a7b631..0000000 --- a/src/test/java/io/github/diegourban/algorithms/sorting/SelectionSortTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.diegourban.algorithms.sorting; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class SelectionSortTest { - - @Test - void test() { - final int[] unsortedItems = {5, 7, 2, 9, 4, 8, 1, 10, 3, 6}; - final int[] sortedItems = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - - SelectionSort selectionSort = new SelectionSort(); - selectionSort.sort(unsortedItems, unsortedItems.length); - - assertThat(unsortedItems).isEqualTo(sortedItems); - } -} diff --git a/src/test/java/io/github/diegourban/algorithms/sorting/SorterPropertyTest.java b/src/test/java/io/github/diegourban/algorithms/sorting/SorterPropertyTest.java new file mode 100644 index 0000000..dcdd5d5 --- /dev/null +++ b/src/test/java/io/github/diegourban/algorithms/sorting/SorterPropertyTest.java @@ -0,0 +1,32 @@ +package io.github.diegourban.algorithms.sorting; + +import net.jqwik.api.Arbitraries; +import net.jqwik.api.Arbitrary; +import net.jqwik.api.ForAll; +import net.jqwik.api.Property; +import net.jqwik.api.Provide; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Every {@link Sorter} must agree with {@link Arrays#sort(int[])} on any input. + */ +class SorterPropertyTest { + + @Provide + Arbitrary sorters() { + return Arbitraries.of(new InsertionSort(), new SelectionSort(), new MergeSort(), new QuickSort()); + } + + @Property + void agreesWithTheJdk(@ForAll("sorters") Sorter sorter, @ForAll int[] items) { + var expected = items.clone(); + Arrays.sort(expected); + + sorter.sort(items); + + assertThat(items).as(sorter.getClass().getSimpleName()).containsExactly(expected); + } +} diff --git a/src/test/java/io/github/diegourban/algorithms/sorting/SorterTest.java b/src/test/java/io/github/diegourban/algorithms/sorting/SorterTest.java new file mode 100644 index 0000000..abc10a5 --- /dev/null +++ b/src/test/java/io/github/diegourban/algorithms/sorting/SorterTest.java @@ -0,0 +1,52 @@ +package io.github.diegourban.algorithms.sorting; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Example-based checks run against every {@link Sorter}; {@link SorterPropertyTest} covers random inputs. + */ +class SorterTest { + + static Stream sorters() { + return Stream.of(new InsertionSort(), new SelectionSort(), new MergeSort(), new QuickSort()); + } + + private static int[] sorted(Sorter sorter, int... items) { + sorter.sort(items); + return items; + } + + @ParameterizedTest + @MethodSource("sorters") + void sortsEvenLengthInput(Sorter sorter) { + assertThat(sorted(sorter, 4, 5, 1, 9, 2, 3, 6, 7)).containsExactly(1, 2, 3, 4, 5, 6, 7, 9); + } + + @ParameterizedTest + @MethodSource("sorters") + void sortsOddLengthInput(Sorter sorter) { + assertThat(sorted(sorter, 4, 1, 8, 9, 10, 2, 3, 7, 6)).containsExactly(1, 2, 3, 4, 6, 7, 8, 9, 10); + assertThat(sorted(sorter, 4, 5, 2, 9, 1, 7, 6, 3, 10)).containsExactly(1, 2, 3, 4, 5, 6, 7, 9, 10); + } + + @ParameterizedTest + @MethodSource("sorters") + void keepsDuplicatesAndNegatives(Sorter sorter) { + assertThat(sorted(sorter, 3, -1, 3, 0, -1, Integer.MIN_VALUE, Integer.MAX_VALUE)) + .containsExactly(Integer.MIN_VALUE, -1, -1, 0, 3, 3, Integer.MAX_VALUE); + } + + @ParameterizedTest + @MethodSource("sorters") + void handlesTrivialInputs(Sorter sorter) { + assertThat(sorted(sorter)).isEmpty(); + assertThat(sorted(sorter, 42)).containsExactly(42); + assertThat(sorted(sorter, 1, 2, 3)).containsExactly(1, 2, 3); + assertThat(sorted(sorter, 3, 2, 1)).containsExactly(1, 2, 3); + } +} From efc68078f1ef9bfbf0b3f513eae2241c074d7e51 Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 15:32:29 +0100 Subject: [PATCH 2/4] Make BinarySearch a static indexOf with property tests BinarySearch.indexOf(int[] sorted, int key) returns an index or -1. Iterative, overflow-safe midpoint. The old find(items, from, to, key) read past the end of the array for keys above the maximum when called with to = length, as its tests did. Co-Authored-By: Claude Opus 5.5 --- README.md | 6 +-- .../algorithms/search/BinarySearch.java | 47 +++++++++--------- .../search/BinarySearchPropertyTest.java | 37 ++++++++++++++ .../algorithms/search/BinarySearchTest.java | 49 ++++++++++--------- 4 files changed, 88 insertions(+), 51 deletions(-) create mode 100644 src/test/java/io/github/diegourban/algorithms/search/BinarySearchPropertyTest.java diff --git a/README.md b/README.md index cafe86d..ae7f21c 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,12 @@ src/main/java/io/github/diegourban/algorithms/ datastructure/ array (shuffle), binarytree, linkedlist, queue, set, stack, vector io/ Reader: fast tokenising stdin reader for judge-style input math/ Algebra (gcd, lcm, primes, factorisation), Combinatorics, Geometry, Numbers, Statistics - search/ BinarySearch - sorting/ Insertion, Merge, Quick and Selection sort + search/ BinarySearch.indexOf + sorting/ Sorter interface: Insertion, Merge, Quick and Selection sort string/ StringUtils src/test/java/io/github/diegourban/algorithms/ - unit tests for the library + unit tests for the library (*PropertyTest: jqwik property-based tests) problems/ worked problems (see below) testing/ SampleBuilder, SampleLoader: helpers for judge-style tests ``` diff --git a/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java b/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java index 1a2247d..af4788d 100644 --- a/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java +++ b/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java @@ -1,35 +1,34 @@ package io.github.diegourban.algorithms.search; /** - * Binary Search algorithm + * Binary search over a sorted {@code int[]}. */ -public class BinarySearch { +public final class BinarySearch { + + private BinarySearch() { + } /** - * Finds an element in a list - *

- * Complexity: O(log n) time and O(1) space + * Finds {@code key} in {@code sorted} by halving the search range: O(log n) time, O(1) space. * - * @param items an ordered list of items to find - * @param from the index to find from in the list - * @param to the index to find to in the list - * @param element the element to find - * @return the index of the element, -1 otherwise + * @param sorted values in ascending order + * @param key the value to look for + * @return an index holding {@code key} (any one of them if it occurs more than once), or -1 if absent */ - public int find(final int[] items, final int from, final int to, final int element) { - if(from > to) { - return -1; - } - - final int middle = (from + to) / 2; - final int middleItem = items[middle]; - - if(element == middleItem) { - return middle; - } - if(element < middleItem) { - return find(items, from, middle - 1, element); + public static int indexOf(int[] sorted, int key) { + var low = 0; + var high = sorted.length - 1; + while (low <= high) { + var middle = (low + high) >>> 1; // unsigned shift: no overflow when low + high > Integer.MAX_VALUE + var value = sorted[middle]; + if (value < key) { + low = middle + 1; + } else if (value > key) { + high = middle - 1; + } else { + return middle; + } } - return find(items, middle + 1, to, element); + return -1; } } diff --git a/src/test/java/io/github/diegourban/algorithms/search/BinarySearchPropertyTest.java b/src/test/java/io/github/diegourban/algorithms/search/BinarySearchPropertyTest.java new file mode 100644 index 0000000..546d7ab --- /dev/null +++ b/src/test/java/io/github/diegourban/algorithms/search/BinarySearchPropertyTest.java @@ -0,0 +1,37 @@ +package io.github.diegourban.algorithms.search; + +import net.jqwik.api.ForAll; +import net.jqwik.api.Property; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link BinarySearch#indexOf(int[], int)} must agree with {@link Arrays#binarySearch(int[], int)} on whether the key + * is present. With duplicates the two may pick different indexes, so only the found value is compared. + */ +class BinarySearchPropertyTest { + + @Property + void agreesWithTheJdk(@ForAll int[] items, @ForAll int key) { + Arrays.sort(items); + + var index = BinarySearch.indexOf(items, key); + + if (Arrays.binarySearch(items, key) >= 0) { + assertThat(items[index]).isEqualTo(key); + } else { + assertThat(index).isEqualTo(-1); + } + } + + @Property + void findsEveryElementThatIsPresent(@ForAll int[] items) { + Arrays.sort(items); + + for (var item : items) { + assertThat(items[BinarySearch.indexOf(items, item)]).isEqualTo(item); + } + } +} diff --git a/src/test/java/io/github/diegourban/algorithms/search/BinarySearchTest.java b/src/test/java/io/github/diegourban/algorithms/search/BinarySearchTest.java index c4a94ae..99e9811 100644 --- a/src/test/java/io/github/diegourban/algorithms/search/BinarySearchTest.java +++ b/src/test/java/io/github/diegourban/algorithms/search/BinarySearchTest.java @@ -6,36 +6,37 @@ class BinarySearchTest { + private static final int[] ONE_TO_TEN = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + @Test + void findsOnTheLeftSide() { + assertThat(BinarySearch.indexOf(ONE_TO_TEN, 3)).isEqualTo(2); + } + + @Test + void findsOnTheRightSide() { + assertThat(BinarySearch.indexOf(ONE_TO_TEN, 9)).isEqualTo(8); + } + + @Test + void findsBothEnds() { + assertThat(BinarySearch.indexOf(ONE_TO_TEN, 1)).isZero(); + assertThat(BinarySearch.indexOf(ONE_TO_TEN, 10)).isEqualTo(9); + } + @Test - void shouldHandleNotExistingElement() { - final int[] items = {1, 2, 3, 5, 6, 7, 8, 9, 10}; - final int element = 4; - final int expectedPosition = -1; - - final BinarySearch binarySearch = new BinarySearch(); - final int position = binarySearch.find(items, 0, items.length, element); - assertThat(position).isEqualTo(expectedPosition); + void returnsMinusOneForAMissingKey() { + assertThat(BinarySearch.indexOf(new int[]{1, 2, 3, 5, 6, 7, 8, 9, 10}, 4)).isEqualTo(-1); } @Test - void shouldFindOnRightSide() { - final int[] items = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - final int element = 9; - final int expectedPosition = 8; - - final BinarySearch binarySearch = new BinarySearch(); - final int position = binarySearch.find(items, 0, items.length, element); - assertThat(position).isEqualTo(expectedPosition); + void returnsMinusOneOutsideTheRange() { + assertThat(BinarySearch.indexOf(ONE_TO_TEN, 0)).isEqualTo(-1); + assertThat(BinarySearch.indexOf(ONE_TO_TEN, 11)).isEqualTo(-1); } @Test - void shouldFindOnLeftSide() { - final int[] items = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - final int element = 3; - final int expectedPosition = 2; - - final BinarySearch binarySearch = new BinarySearch(); - final int position = binarySearch.find(items, 0, items.length, element); - assertThat(position).isEqualTo(expectedPosition); + void returnsMinusOneForAnEmptyArray() { + assertThat(BinarySearch.indexOf(new int[]{}, 1)).isEqualTo(-1); } } From be9e425902c59ae2fb3784115dd51a4945c97761 Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 15:32:39 +0100 Subject: [PATCH 3/4] Ignore the local jqwik failure database Co-Authored-By: Claude Opus 5.5 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 046b999..1611de9 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,6 @@ fabric.properties hot.md .claude/worktrees/ triage-*.md + +# jqwik failure database (replays previously failing samples locally) +.jqwik-database From 73a7b44c9e675a93ccc6d389801def7b09f1e716 Mon Sep 17 00:00:00 2001 From: Diego Leonardo Urban Date: Fri, 6 Nov 2026 15:38:08 +0100 Subject: [PATCH 4/4] Restore the original recursive BinarySearch behind indexOf Keeps the agreed static indexOf API but goes back to the existing recursive find (now a private helper) instead of an iterative rewrite. indexOf calls it with an inclusive upper bound of length - 1. Co-Authored-By: Claude Opus 5.5 --- .../algorithms/search/BinarySearch.java | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java b/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java index af4788d..55a3694 100644 --- a/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java +++ b/src/main/java/io/github/diegourban/algorithms/search/BinarySearch.java @@ -1,34 +1,46 @@ package io.github.diegourban.algorithms.search; /** - * Binary search over a sorted {@code int[]}. + * Binary Search algorithm */ -public final class BinarySearch { +public class BinarySearch { - private BinarySearch() { + /** + * Finds an element in an ordered list + * + * @param sorted an ordered list of items to find + * @param key the element to find + * @return the index of the element, -1 otherwise + */ + public static int indexOf(final int[] sorted, final int key) { + return find(sorted, 0, sorted.length - 1, key); } /** - * Finds {@code key} in {@code sorted} by halving the search range: O(log n) time, O(1) space. + * Finds an element in a list + *

+ * Complexity: O(log n) time and O(1) space * - * @param sorted values in ascending order - * @param key the value to look for - * @return an index holding {@code key} (any one of them if it occurs more than once), or -1 if absent + * @param items an ordered list of items to find + * @param from the index to find from in the list + * @param to the index to find to in the list + * @param element the element to find + * @return the index of the element, -1 otherwise */ - public static int indexOf(int[] sorted, int key) { - var low = 0; - var high = sorted.length - 1; - while (low <= high) { - var middle = (low + high) >>> 1; // unsigned shift: no overflow when low + high > Integer.MAX_VALUE - var value = sorted[middle]; - if (value < key) { - low = middle + 1; - } else if (value > key) { - high = middle - 1; - } else { - return middle; - } + private static int find(final int[] items, final int from, final int to, final int element) { + if(from > to) { + return -1; + } + + final int middle = (from + to) / 2; + final int middleItem = items[middle]; + + if(element == middleItem) { + return middle; + } + if(element < middleItem) { + return find(items, from, middle - 1, element); } - return -1; + return find(items, middle + 1, to, element); } }