Skip to content

Commit

Permalink
Add in-place QuickSort and use it in SortedIterationKTypeVTypeHashMap.
Browse files Browse the repository at this point in the history
  • Loading branch information
bruno-roustant committed Oct 19, 2021
1 parent 415c176 commit d882975
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 165 deletions.
3 changes: 3 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

** New features and API changes

GH-31: Added QuickSort and used it in SortedIterationKTypeVTypeHashMap. (Bruno Roustant)
QuickSort can be used with custom element comparator and swapper.

GH-28: Added SortedIterationKTypeVTypeHashMap: a sorted-iteration order view over
another key-value map. (Bruno Roustant)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ private IndirectSort() {
* Returns the order of elements between indices <code>start</code> and <code>length</code>, as
* indicated by the given <code>comparator</code>.
*
* <p>This routine uses merge sort. It is guaranteed to be stable.
* <p>This routine uses merge sort. It is guaranteed to be stable. It creates a new indices array,
* and clones it while sorting.
*/
public static int[] mergesort(int start, int length, IntBinaryOperator comparator) {
final int[] src = createOrderArray(start, length);
Expand All @@ -44,7 +45,8 @@ public static int[] mergesort(int start, int length, IntBinaryOperator comparato
/**
* Returns a sorted copy of the order array provided, using the given <code>comparator</code>.
*
* <p>This routine uses merge sort. It is guaranteed to be stable.
* <p>This routine uses merge sort. It is guaranteed to be stable. The provided {@code
* indicesArray} is cloned while sorting and the clone is returned.
*/
public static int[] mergesort(int[] orderArray, IntBinaryOperator comparator) {
if (orderArray.length <= 1) {
Expand All @@ -59,7 +61,8 @@ public static int[] mergesort(int[] orderArray, IntBinaryOperator comparator) {
* Returns the order of elements between indices <code>start</code> and <code>length</code>, as
* indicated by the given <code>comparator</code>.
*
* <p>This routine uses merge sort. It is guaranteed to be stable.
* <p>This routine uses merge sort. It is guaranteed to be stable. It creates a new indices array,
* and clones it while sorting.
*/
public static <T> int[] mergesort(
T[] input, int start, int length, Comparator<? super T> comparator) {
Expand Down
174 changes: 174 additions & 0 deletions hppc/src/main/java/com/carrotsearch/hppc/sorting/QuickSort.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* HPPC
*
* Copyright (C) 2010-2020 Carrot Search s.c.
* All rights reserved.
*
* Refer to the full license file "LICENSE.txt":
* https://github.com/carrotsearch/hppc/blob/master/LICENSE.txt
*/
package com.carrotsearch.hppc.sorting;

import java.util.function.IntBinaryOperator;

/**
* In-place Quick sort with 3-way partitioning and ending with Insertion sort.
*
* <p>The sorting is not stable. Performance is O(n.log(n)) and memory is O(1) (although recursion
* memory is O(log(n))).
*/
public final class QuickSort {

/** Below this size threshold, the sub-range is sorted using Insertion sort. */
static final int INSERTION_SORT_THRESHOLD = 16;

/** Below this size threshold, the partition selection is simplified to taking the middle as the pivot. */
static final int MIDDLE_PIVOT_THRESHOLD = 40;

/** No instantiation. */
private QuickSort() {
// No instantiation.
}

/** @see #sort(int, int, IntBinaryOperator, IntBinaryOperator) */
public static void sort(int[] array, IntBinaryOperator comparator) {
sort(array, 0, array.length, comparator);
}

/** @see #sort(int, int, IntBinaryOperator, IntBinaryOperator) */
public static void sort(int[] array, int fromIndex, int toIndex, IntBinaryOperator comparator) {
sort(
fromIndex,
toIndex,
comparator,
(i, j) -> {
int swap = array[i];
array[i] = array[j];
array[j] = swap;
return 0;
});
}

/**
* Performs a recursive in-place Quick sort. The sorting is not stable.
*
* @param fromIndex Index where to start sorting in the array, inclusive.
* @param toIndex Index where to stop sorting in the array, exclusive.
* @param comparator Compares elements based on their indices. Given indices i and j in the
* provided array, this comparator returns respectively -1/0/1 if the element at index i is
* respectively less/equal/greater than the element at index j.
* @param swapper Swaps the elements in the array at the given indices. For example, a custom
* swapper may allow sorting two arrays simultaneously.
*/
public static void sort(
int fromIndex, int toIndex, IntBinaryOperator comparator, IntBinaryOperator swapper) {
sortInner(fromIndex, toIndex - 1, comparator, swapper);
}

/**
* @param start Start index, inclusive.
* @param end End index, inclusive.
* @param c Compares elements based on their indices.
* @param s Swaps the elements in the array at the given indices.
*/
private static void sortInner(
int start, int end, IntBinaryOperator c, IntBinaryOperator s) {
int size;
while ((size = end - start + 1) > INSERTION_SORT_THRESHOLD) {

// Pivot selection.
int middle = (start + end) >>> 1;
int pivot;
if (size <= MIDDLE_PIVOT_THRESHOLD) {
// Select the middle as the pivot.
// If we select the median of [start, middle, end] as the pivot there is a performance
// degradation if the array is in descending order.
pivot = middle;
} else {
// Select the pivot with the median of medians.
int range = size >> 3;
int doubleRange = range << 1;
int medianStart = median(start, start + range, start + doubleRange, c);
int medianMiddle = median(middle - range, middle, middle + range, c);
int medianEnd = median(end - doubleRange, end - range, end, c);
pivot = median(medianStart, medianMiddle, medianEnd, c);
}

// 3-way partitioning.
swap(start, pivot, s);
int i = start;
int j = end + 1;
int p = start + 1;
int q = end;
while (true) {
while (++i < end && comp(i, start, c) < 0) ;
while (--j > start && comp(j, start, c) > 0) ;
if (i >= j) {
if (i == j && comp(i, start, c) == 0) {
swap(i, p, s);
}
break;
}
swap(i, j, s);
if (comp(i, start, c) == 0) {
swap(i, p++, s);
}
if (comp(j, start, c) == 0) {
swap(j, q--, s);
}
}
i = j + 1;
for (int k = start; k < p; k++) {
swap(k, j--, s);
}
for (int k = end; k > q; k--) {
swap(k, i++, s);
}

// Recursion on the smallest partition.
// Replace the tail recursion by a loop.
if (j - start < end - i) {
sortInner(start, j, c, s);
start = i;
} else {
sortInner(i, end, c, s);
end = j;
}
}

insertionSort(start, end, c, s);
}

/** Sorts from start to end indices inclusive with insertion sort. */
private static void insertionSort(int start, int end, IntBinaryOperator c, IntBinaryOperator s) {
for (int i = start + 1; i <= end; i++) {
for (int j = i; j > start && comp(j - 1, j, c) > 0; j--) {
swap(j - 1, j, s);
}
}
}

/** Returns the index of the median element among three elements at provided indices. */
private static int median(int i, int j, int k, IntBinaryOperator c) {
if (comp(i, j, c) < 0) {
if (comp(j, k, c) <= 0) {
return j;
}
return comp(i, k, c) < 0 ? k : i;
}
if (comp(j, k, c) >= 0) {
return j;
}
return comp(i, k, c) < 0 ? i : k;
}

/** Compares two elements at provided indices. */
private static int comp(int i, int j, IntBinaryOperator comparator) {
return comparator.applyAsInt(i, j);
}

/** Swaps two elements at provided indices. */
private static void swap(int i, int j, IntBinaryOperator swapper) {
swapper.applyAsInt(i, j);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.carrotsearch.hppc.cursors.*;
import com.carrotsearch.hppc.predicates.*;
import com.carrotsearch.hppc.procedures.*;
import com.carrotsearch.hppc.sorting.IndirectSort;
import com.carrotsearch.hppc.sorting.QuickSort;

/*! #if ($TemplateOptions.KTypeGeneric) !*/
import java.util.Comparator;
Expand Down Expand Up @@ -79,24 +79,28 @@ private int[] createEntryIndexes() {
protected int[] sortIterationOrder(int[] entryIndexes,
/*! #if ($TemplateOptions.KTypeGeneric) !*/ Comparator<KType> /*! #else KTypeComparator<KType> #end !*/ comparator
) {
return IndirectSort.mergesort(entryIndexes, (a, b) -> {
QuickSort.sort(entryIndexes, (i, j) -> {
KType[] keys = Intrinsics.<KType[]> cast(delegate.keys);
return comparator.compare(keys[a], keys[b]);
return comparator.compare(keys[entryIndexes[i]], keys[entryIndexes[j]]);
});
return entryIndexes;
}

/**
* Sort the iteration order array based on the provided comparator on keys and values.
*/
protected int[] sortIterationOrder(int[] entryIndexes, KTypeVTypeComparator<KType, VType> comparator) {
return IndirectSort.mergesort(entryIndexes, new IntBinaryOperator() {
QuickSort.sort(entryIndexes, new IntBinaryOperator() {
final KType[] keys = Intrinsics.<KType[]> cast(delegate.keys);
final VType[] values = Intrinsics.<VType[]> cast(delegate.values);
@Override
public int applyAsInt(int a, int b) {
return comparator.compare(keys[a], values[a], keys[b], values[b]);
public int applyAsInt(int i, int j) {
int index1 = entryIndexes[i];
int index2 = entryIndexes[j];
return comparator.compare(keys[index1], values[index1], keys[index2], values[index2]);
}
});
return entryIndexes;
}

@Override
Expand Down
Loading

0 comments on commit d882975

Please sign in to comment.