From 745ea2ccc595e6fc9a55ba1df1c96de8bb91d12c Mon Sep 17 00:00:00 2001 From: Nils Goldmann Date: Sun, 5 Dec 2021 21:44:51 +0100 Subject: [PATCH 1/2] added JUnit 5 Maven dependencies --- pom.xml | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 53e8796e5007..48f9e1cbbbc6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 com.thealgorithms Java @@ -10,4 +12,37 @@ 17 17 + + + + + org.junit + junit-bom + 5.8.2 + pom + import + + + + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + maven-compiler-plugin + 3.8.1 + + + maven-surefire-plugin + 2.22.2 + + + \ No newline at end of file From 1d41a7fc39d5eab74ab84554ef489613cb2be46d Mon Sep 17 00:00:00 2001 From: Nils Goldmann Date: Sun, 5 Dec 2021 21:49:59 +0100 Subject: [PATCH 2/2] added QuickSelect algorithm + tests --- .../thealgorithms/searches/QuickSelect.java | 140 ++++++++++ .../searches/QuickSelectTest.java | 240 ++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 src/main/java/com/thealgorithms/searches/QuickSelect.java create mode 100644 src/test/java/com/thealgorithms/searches/QuickSelectTest.java diff --git a/src/main/java/com/thealgorithms/searches/QuickSelect.java b/src/main/java/com/thealgorithms/searches/QuickSelect.java new file mode 100644 index 000000000000..fee11cabbcee --- /dev/null +++ b/src/main/java/com/thealgorithms/searches/QuickSelect.java @@ -0,0 +1,140 @@ +package com.thealgorithms.searches; + +import java.util.*; + +/** + * An implementation of the Quickselect algorithm as described + * here. + */ +public final class QuickSelect { + + /** + * Selects the {@code n}-th largest element of {@code list}, i.e. the element that would + * be at index n if the list was sorted. + *

+ * Calling this function might change the order of elements in {@code list}. + * + * @param list the list of elements + * @param n the index + * @param the type of list elements + * @return the n-th largest element in the list + * @throws IndexOutOfBoundsException if n is less than 0 or greater or equal to + * the number of elements in the list + * @throws IllegalArgumentException if the list is empty + * @throws NullPointerException if {@code list} is null + */ + public static > T select(List list, int n) { + Objects.requireNonNull(list, "The list of elements must not be null."); + + if (list.size() == 0) { + String msg = "The list of elements must not be empty."; + throw new IllegalArgumentException(msg); + } + + if (n < 0) { + String msg = "The index must not be negative."; + throw new IndexOutOfBoundsException(msg); + } + + if (n >= list.size()) { + String msg = "The index must be less than the number of elements."; + throw new IndexOutOfBoundsException(msg); + } + + int index = selectIndex(list, n); + return list.get(index); + } + + private static > int selectIndex(List list, int n) { + return selectIndex(list, 0, list.size() - 1, n); + } + + private static > int selectIndex( + List list, + int left, + int right, + int n + ) { + while (true) { + if (left == right) + return left; + int pivotIndex = pivot(list, left, right); + pivotIndex = partition(list, left, right, pivotIndex, n); + if (n == pivotIndex) { + return n; + } else if (n < pivotIndex) { + right = pivotIndex - 1; + } else { + left = pivotIndex + 1; + } + } + } + + private static > int partition( + List list, + int left, + int right, + int pivotIndex, + int n + ) { + T pivotValue = list.get(pivotIndex); + Collections.swap(list, pivotIndex, right); + int storeIndex = left; + + for (int i = left; i < right; i++) { + if (list.get(i).compareTo(pivotValue) < 0) { + Collections.swap(list, storeIndex, i); + storeIndex++; + } + } + + int storeIndexEq = storeIndex; + + for (int i = storeIndex; i < right; i++) { + if (list.get(i).compareTo(pivotValue) == 0) { + Collections.swap(list, storeIndexEq, i); + storeIndexEq++; + } + } + + Collections.swap(list, right, storeIndexEq); + + return (n < storeIndex) + ? storeIndex + : Math.min(n, storeIndexEq); + } + + private static > int pivot( + List list, + int left, + int right + ) { + if (right - left < 5) { + return partition5(list, left, right); + } + + for (int i = left; i < right; i += 5) { + int subRight = i + 4; + if (subRight > right) { + subRight = right; + } + int median5 = partition5(list, i, subRight); + int rightIndex = left + (i - left) / 5; + Collections.swap(list, median5, rightIndex); + } + + int mid = (right - left) / 10 + left + 1; + int rightIndex = left + (right - left) / 5; + return selectIndex(list, left, rightIndex, mid); + } + + private static > int partition5( + List list, + int left, + int right + ) { + List ts = list.subList(left, right); + ts.sort(Comparator.naturalOrder()); + return (left + right) >>> 1; + } +} diff --git a/src/test/java/com/thealgorithms/searches/QuickSelectTest.java b/src/test/java/com/thealgorithms/searches/QuickSelectTest.java new file mode 100644 index 000000000000..588b20f54545 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/QuickSelectTest.java @@ -0,0 +1,240 @@ +package com.thealgorithms.searches; + +import org.junit.jupiter.api.Test; + +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +class QuickSelectTest { + @Test + void quickSelectMinimumOfOneElement() { + List elements = Collections.singletonList(42); + int minimum = QuickSelect.select(elements, 0); + assertEquals(42, minimum); + } + + @Test + void quickSelectMinimumOfTwoElements() { + List elements1 = Arrays.asList(42, 90); + List elements2 = Arrays.asList(90, 42); + + int minimum1 = QuickSelect.select(elements1, 0); + int minimum2 = QuickSelect.select(elements2, 0); + + assertEquals(42, minimum1); + assertEquals(42, minimum2); + } + + @Test + void quickSelectMinimumOfThreeElements() { + List elements1 = Arrays.asList(1, 2, 3); + List elements2 = Arrays.asList(2, 1, 3); + List elements3 = Arrays.asList(2, 3, 1); + + int minimum1 = QuickSelect.select(elements1, 0); + int minimum2 = QuickSelect.select(elements2, 0); + int minimum3 = QuickSelect.select(elements3, 0); + + assertEquals(1, minimum1); + assertEquals(1, minimum2); + assertEquals(1, minimum3); + } + + @Test + void quickSelectMinimumOfManyElements() { + List elements = generateRandomIntegers(NUM_RND_ELEMENTS); + int actual = QuickSelect.select(elements, 0); + int expected = elements.stream().min(Comparator.naturalOrder()).get(); + assertEquals(expected, actual); + } + + @Test + void quickSelectMaximumOfOneElement() { + List elements = Collections.singletonList(42); + int maximum = QuickSelect.select(elements, 0); + assertEquals(42, maximum); + } + + @Test + void quickSelectMaximumOfTwoElements() { + List elements1 = Arrays.asList(42, 90); + List elements2 = Arrays.asList(90, 42); + + int maximum1 = QuickSelect.select(elements1, 1); + int maximum2 = QuickSelect.select(elements2, 1); + + assertEquals(90, maximum1); + assertEquals(90, maximum2); + } + + @Test + void quickSelectMaximumOfThreeElements() { + List elements1 = Arrays.asList(1, 2, 3); + List elements2 = Arrays.asList(2, 1, 3); + List elements3 = Arrays.asList(2, 3, 1); + + int maximum1 = QuickSelect.select(elements1, 2); + int maximum2 = QuickSelect.select(elements2, 2); + int maximum3 = QuickSelect.select(elements3, 2); + + assertEquals(3, maximum1); + assertEquals(3, maximum2); + assertEquals(3, maximum3); + } + + @Test + void quickSelectMaximumOfManyElements() { + List elements = generateRandomIntegers(NUM_RND_ELEMENTS); + int actual = QuickSelect.select(elements, NUM_RND_ELEMENTS - 1); + int expected = elements.stream().max(Comparator.naturalOrder()).get(); + assertEquals(expected, actual); + } + + @Test + void quickSelectMedianOfOneElement() { + List elements = Collections.singletonList(42); + int median = QuickSelect.select(elements, 0); + assertEquals(42, median); + } + + @Test + void quickSelectMedianOfThreeElements() { + List elements1 = Arrays.asList(1, 2, 3); + List elements2 = Arrays.asList(2, 1, 3); + List elements3 = Arrays.asList(2, 3, 1); + + int median1 = QuickSelect.select(elements1, 1); + int median2 = QuickSelect.select(elements2, 1); + int median3 = QuickSelect.select(elements3, 1); + + assertEquals(2, median1); + assertEquals(2, median2); + assertEquals(2, median3); + } + + @Test + void quickSelectMedianOfManyElements() { + int medianIndex = NUM_RND_ELEMENTS / 2; + List elements = generateRandomIntegers(NUM_RND_ELEMENTS); + int actual = QuickSelect.select(elements, medianIndex); + + List elementsSorted = getSortedCopyOfList(elements); + assertEquals(elementsSorted.get(medianIndex), actual); + } + + @Test + void quickSelect30thPercentileOf10Elements() { + List elements = generateRandomIntegers(10); + int actual = QuickSelect.select(elements, 2); + + List elementsSorted = getSortedCopyOfList(elements); + assertEquals(elementsSorted.get(2), actual); + } + + @Test + void quickSelect30thPercentileOfManyElements() { + int percentile30th = NUM_RND_ELEMENTS / 10 * 3; + List elements = generateRandomIntegers(NUM_RND_ELEMENTS); + int actual = QuickSelect.select(elements, percentile30th); + + List elementsSorted = getSortedCopyOfList(elements); + assertEquals(elementsSorted.get(percentile30th), actual); + } + + @Test + void quickSelect70thPercentileOf10Elements() { + List elements = generateRandomIntegers(10); + int actual = QuickSelect.select(elements, 6); + + List elementsSorted = getSortedCopyOfList(elements); + assertEquals(elementsSorted.get(6), actual); + } + + @Test + void quickSelect70thPercentileOfManyElements() { + int percentile70th = NUM_RND_ELEMENTS / 10 * 7; + List elements = generateRandomIntegers(NUM_RND_ELEMENTS); + int actual = QuickSelect.select(elements, percentile70th); + + List elementsSorted = getSortedCopyOfList(elements); + assertEquals(elementsSorted.get(percentile70th), actual); + } + + @Test + void quickSelectMedianOfThreeCharacters() { + List elements = Arrays.asList('X', 'Z', 'Y'); + char actual = QuickSelect.select(elements, 1); + assertEquals(actual, 'Y'); + } + + @Test + void quickSelectMedianOfManyCharacters() { + List elements = generateRandomCharacters(NUM_RND_ELEMENTS); + char actual = QuickSelect.select(elements, NUM_RND_ELEMENTS / 30); + + List elementsSorted = getSortedCopyOfList(elements); + assertEquals(elementsSorted.get(NUM_RND_ELEMENTS / 30), actual); + } + + @Test + void quickSelectNullList() { + NullPointerException exception = assertThrows( + NullPointerException.class, + () -> QuickSelect.select(null, 0) + ); + String expectedMsg = "The list of elements must not be null."; + assertEquals(expectedMsg, exception.getMessage()); + } + + @Test + void quickSelectEmptyList() { + List objects = Collections.emptyList(); + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> QuickSelect.select(objects, 0) + ); + String expectedMsg = "The list of elements must not be empty."; + assertEquals(expectedMsg, exception.getMessage()); + } + + @Test + void quickSelectIndexOutOfLeftBound() { + IndexOutOfBoundsException exception = assertThrows( + IndexOutOfBoundsException.class, + () -> QuickSelect.select(Collections.singletonList(1), -1) + ); + String expectedMsg = "The index must not be negative."; + assertEquals(expectedMsg, exception.getMessage()); + } + + @Test + void quickSelectIndexOutOfRightBound() { + IndexOutOfBoundsException exception = assertThrows( + IndexOutOfBoundsException.class, + () -> QuickSelect.select(Collections.singletonList(1), 1) + ); + String expectedMsg = "The index must be less than the number of elements."; + assertEquals(expectedMsg, exception.getMessage()); + } + + private static final int NUM_RND_ELEMENTS = 99; + private static final Random RANDOM = new Random(42); + private static final int ASCII_A = 0x41; + private static final int ASCII_Z = 0x5A; + + private static List generateRandomIntegers(int n) { + return RANDOM.ints(n).boxed().collect(Collectors.toList()); + } + + private static List generateRandomCharacters(int n) { + return RANDOM.ints(n, ASCII_A, ASCII_Z) + .mapToObj(i -> (char) i) + .collect(Collectors.toList()); + } + + private static > List getSortedCopyOfList(List list) { + return list.stream().sorted().collect(Collectors.toList()); + } +}