From 6e23e198ab30bbba5cdbf74d5282f98080083f3d Mon Sep 17 00:00:00 2001 From: Alex Klymenko Date: Thu, 8 Aug 2024 09:45:33 +0200 Subject: [PATCH] feat: `SpreadSort` implementation (#5308) --- DIRECTORY.md | 2 + .../com/thealgorithms/sorts/SpreadSort.java | 273 ++++++++++++++++++ .../thealgorithms/sorts/SpreadSortTest.java | 37 +++ 3 files changed, 312 insertions(+) create mode 100644 src/main/java/com/thealgorithms/sorts/SpreadSort.java create mode 100644 src/test/java/com/thealgorithms/sorts/SpreadSortTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index 7e726f319..22453235b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -523,6 +523,7 @@ * [SortAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java) * [SortUtils](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtils.java) * [SortUtilsRandomGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtilsRandomGenerator.java) + * [SpreadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SpreadSort.java) * [StoogeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StoogeSort.java) * [StrandSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StrandSort.java) * [SwapSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SwapSort.java) @@ -893,6 +894,7 @@ * [SortingAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortingAlgorithmTest.java) * [SortUtilsRandomGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsRandomGeneratorTest.java) * [SortUtilsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsTest.java) + * [SpreadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java) * [StoogeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StoogeSortTest.java) * [StrandSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StrandSortTest.java) * [SwapSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SwapSortTest.java) diff --git a/src/main/java/com/thealgorithms/sorts/SpreadSort.java b/src/main/java/com/thealgorithms/sorts/SpreadSort.java new file mode 100644 index 000000000..f1fd24f47 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/SpreadSort.java @@ -0,0 +1,273 @@ +package com.thealgorithms.sorts; +import java.util.Arrays; + +/** + * SpreadSort is a highly efficient sorting algorithm suitable for large datasets. + * It distributes elements into buckets and recursively sorts these buckets. + * This implementation is generic and can sort any array of elements that extend Comparable. + */ +public class SpreadSort implements SortAlgorithm { + private static final int MAX_INSERTION_SORT_THRESHOLD = 1000; + private static final int MAX_INITIAL_BUCKET_CAPACITY = 1000; + private static final int MAX_MIN_BUCKETS = 100; + + private final int insertionSortThreshold; + private final int initialBucketCapacity; + private final int minBuckets; + + /** + * Constructor to initialize the SpreadSort algorithm with custom parameters. + * + * @param insertionSortThreshold the threshold for using insertion sort for small segments (1-1000) + * @param initialBucketCapacity the initial capacity for each bucket (1-1000) + * @param minBuckets the minimum number of buckets to use (1-100) + */ + public SpreadSort(int insertionSortThreshold, int initialBucketCapacity, int minBuckets) { + if (insertionSortThreshold < 1 || insertionSortThreshold > MAX_INSERTION_SORT_THRESHOLD) { + throw new IllegalArgumentException("Insertion sort threshold must be between 1 and " + MAX_INSERTION_SORT_THRESHOLD); + } + if (initialBucketCapacity < 1 || initialBucketCapacity > MAX_INITIAL_BUCKET_CAPACITY) { + throw new IllegalArgumentException("Initial bucket capacity must be between 1 and " + MAX_INITIAL_BUCKET_CAPACITY); + } + if (minBuckets < 1 || minBuckets > MAX_MIN_BUCKETS) { + throw new IllegalArgumentException("Minimum number of buckets must be between 1 and " + MAX_MIN_BUCKETS); + } + + this.insertionSortThreshold = insertionSortThreshold; + this.initialBucketCapacity = initialBucketCapacity; + this.minBuckets = minBuckets; + } + + /** + * Default constructor with predefined values. + */ + public SpreadSort() { + this(16, 16, 2); + } + + /** + * Sorts an array using the SpreadSort algorithm. + * + * @param array the array to be sorted + * @param the type of elements in the array + * @return the sorted array + */ + @Override + public > T[] sort(T[] array) { + if (array.length == 0) { + return array; + } + spreadSort(array, 0, array.length - 1); + return array; + } + + /** + * Internal method to sort an array segment using the SpreadSort algorithm. + * + * @param array the array to be sorted + * @param left the left boundary of the segment + * @param right the right boundary of the segment + * @param the type of elements in the array + */ + private > void spreadSort(final T[] array, final int left, final int right) { + if (left >= right) { + return; + } + + // Base case for small segments + if (right - left < insertionSortThreshold) { + insertionSort(array, left, right); + return; + } + + T min = findMin(array, left, right); + T max = findMax(array, left, right); + + if (min.equals(max)) { + return; // All elements are the same + } + + int numBuckets = calculateNumBuckets(right - left + 1); + final Bucket[] buckets = createBuckets(numBuckets); + + distributeElements(array, left, right, min, max, numBuckets, buckets); + collectElements(array, left, buckets); + } + + /** + * Finds the minimum element in the specified segment of the array. + * + * @param array the array to search + * @param left the left boundary of the segment + * @param right the right boundary of the segment + * @param the type of elements in the array + * @return the minimum element + */ + private > T findMin(final T[] array, final int left, final int right) { + T min = array[left]; + for (int i = left + 1; i <= right; i++) { + if (SortUtils.less(array[i], min)) { + min = array[i]; + } + } + return min; + } + + /** + * Finds the maximum element in the specified segment of the array. + * + * @param array the array to search + * @param left the left boundary of the segment + * @param right the right boundary of the segment + * @param the type of elements in the array + * @return the maximum element + */ + private > T findMax(final T[] array, final int left, final int right) { + T max = array[left]; + for (int i = left + 1; i <= right; i++) { + if (SortUtils.greater(array[i], max)) { + max = array[i]; + } + } + return max; + } + + /** + * Calculates the number of buckets needed based on the size of the segment. + * + * @param segmentSize the size of the segment + * @return the number of buckets + */ + private int calculateNumBuckets(final int segmentSize) { + int numBuckets = segmentSize / insertionSortThreshold; + return Math.max(numBuckets, minBuckets); + } + + /** + * Creates an array of buckets. + * + * @param numBuckets the number of buckets to create + * @param the type of elements in the buckets + * @return an array of buckets + */ + @SuppressWarnings("unchecked") + private > Bucket[] createBuckets(final int numBuckets) { + final Bucket[] buckets = new Bucket[numBuckets]; + for (int i = 0; i < numBuckets; i++) { + buckets[i] = new Bucket<>(initialBucketCapacity); + } + return buckets; + } + + /** + * Distributes elements of the array segment into buckets. + * + * @param array the array to be sorted + * @param left the left boundary of the segment + * @param right the right boundary of the segment + * @param min the minimum element in the segment + * @param max the maximum element in the segment + * @param numBuckets the number of buckets + * @param buckets the array of buckets + * @param the type of elements in the array + */ + private > void distributeElements(final T[] array, final int left, final int right, final T min, final T max, final int numBuckets, final Bucket[] buckets) { + final double range = max.compareTo(min); + for (int i = left; i <= right; i++) { + final int scaleRangeDifference = array[i].compareTo(min) * numBuckets; + int bucketIndex = (int) (scaleRangeDifference / (range + 1)); + buckets[bucketIndex].add(array[i]); + } + } + + /** + * Collects elements from the buckets back into the array. + * + * @param array the array to be sorted + * @param left the left boundary of the segment + * @param buckets the array of buckets + * @param the type of elements in the array + */ + private > void collectElements(final T[] array, final int left, final Bucket[] buckets) { + int index = left; + for (Bucket bucket : buckets) { + if (bucket.size() > 0) { + T[] bucketArray = bucket.toArray(); + spreadSort(bucketArray, 0, bucketArray.length - 1); + for (T element : bucketArray) { + array[index++] = element; + } + } + } + } + + /** + * Insertion sort implementation for small segments. + * + * @param array the array to be sorted + * @param left the left boundary of the segment + * @param right the right boundary of the segment + * @param the type of elements in the array + */ + private > void insertionSort(final T[] array, final int left, final int right) { + for (int i = left + 1; i <= right; i++) { + T key = array[i]; + int j = i - 1; + while (j >= left && SortUtils.greater(array[j], key)) { + array[j + 1] = array[j]; + j--; + } + array[j + 1] = key; + } + } + + /** + * Bucket class to hold elements during sorting. + * + * @param the type of elements in the bucket + */ + private static class Bucket> { + private T[] elements; + private int size; + + /** + * Constructs a new bucket with initial capacity. + */ + @SuppressWarnings("unchecked") + Bucket(int initialBucketCapacity) { + elements = (T[]) new Comparable[initialBucketCapacity]; + size = 0; + } + + /** + * Adds an element to the bucket. + * + * @param element the element to add + */ + void add(T element) { + if (size == elements.length) { + elements = Arrays.copyOf(elements, size * 2); + } + elements[size++] = element; + } + + /** + * Returns the number of elements in the bucket. + * + * @return the size of the bucket + */ + int size() { + return size; + } + + /** + * Returns an array containing all elements in the bucket. + * + * @return an array containing all elements in the bucket + */ + @SuppressWarnings("unchecked") + T[] toArray() { + return Arrays.copyOf(elements, size); + } + } +} diff --git a/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java b/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java new file mode 100644 index 000000000..a4992a02a --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java @@ -0,0 +1,37 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +public class SpreadSortTest extends SortingAlgorithmTest { + + protected int getGeneratedArraySize() { + return 1000; + } + + @Override + SortAlgorithm getSortAlgorithm() { + return new SpreadSort(); + } + + static class ConstructorArgumentsProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(org.junit.jupiter.api.extension.ExtensionContext context) { + return Stream.of(Arguments.of(0, 16, 2, IllegalArgumentException.class), Arguments.of(16, 0, 2, IllegalArgumentException.class), Arguments.of(16, 16, 0, IllegalArgumentException.class), Arguments.of(1001, 16, 2, IllegalArgumentException.class), + Arguments.of(16, 1001, 2, IllegalArgumentException.class), Arguments.of(16, 16, 101, IllegalArgumentException.class)); + } + } + + @ParameterizedTest + @ArgumentsSource(ConstructorArgumentsProvider.class) + void testConstructor(int insertionSortThreshold, int initialBucketCapacity, int minBuckets, Class expectedException) { + Executable executable = () -> new SpreadSort(insertionSortThreshold, initialBucketCapacity, minBuckets); + assertThrows(expectedException, executable); + } +}