Skip to content

feat: SpreadSort implementation #5308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
273 changes: 273 additions & 0 deletions src/main/java/com/thealgorithms/sorts/SpreadSort.java
Original file line number Diff line number Diff line change
@@ -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 <T> the type of elements in the array
* @return the sorted array
*/
@Override
public <T extends Comparable<T>> 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 <T> the type of elements in the array
*/
private <T extends Comparable<T>> 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<T>[] 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 <T> the type of elements in the array
* @return the minimum element
*/
private <T extends Comparable<T>> 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 <T> the type of elements in the array
* @return the maximum element
*/
private <T extends Comparable<T>> 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 <T> the type of elements in the buckets
* @return an array of buckets
*/
@SuppressWarnings("unchecked")
private <T extends Comparable<T>> Bucket<T>[] createBuckets(final int numBuckets) {
final Bucket<T>[] 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 <T> the type of elements in the array
*/
private <T extends Comparable<T>> void distributeElements(final T[] array, final int left, final int right, final T min, final T max, final int numBuckets, final Bucket<T>[] 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 <T> the type of elements in the array
*/
private <T extends Comparable<T>> void collectElements(final T[] array, final int left, final Bucket<T>[] buckets) {
int index = left;
for (Bucket<T> 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 <T> the type of elements in the array
*/
private <T extends Comparable<T>> 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 <T> the type of elements in the bucket
*/
private static class Bucket<T extends Comparable<T>> {
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);
}
}
}
37 changes: 37 additions & 0 deletions src/test/java/com/thealgorithms/sorts/SpreadSortTest.java
Original file line number Diff line number Diff line change
@@ -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<? extends Arguments> 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<Exception> expectedException) {
Executable executable = () -> new SpreadSort(insertionSortThreshold, initialBucketCapacity, minBuckets);
assertThrows(expectedException, executable);
}
}