com.xdev.jadoth.collections.JaSort Maven / Gradle / Ivy
/**
*
*/
package com.xdev.jadoth.collections;
/*-
* #%L
* XDEV Application Framework
* %%
* Copyright (C) 2003 - 2020 XDEV Software
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.util.Arrays;
import java.util.Comparator;
/**
* @author Thomas Muenz
*
*/
public class JaSort
{
///////////////////////////////////////////////////////////////////////////
// Quicksort //
/////////////////////
/*
Quicksort algorithm for int with comments
---------------------------------
/*
* The threshold for using insertion sort depends on the data type and maybe hardware as well.
* Current tests for int values showed values between 20 and 25 as best threshold, while for Objects, it's still 7.
*\
if(endIndex - startIndex < 20) { //this actually is "length - 1"
for(int i = startIndex; i <= endIndex; i++){
for(int j = i; j > startIndex && values[j-1] > values[j]; j--){
final int t = values[j];
values[j] = values[j-1];
values[j-1] = t;
}
}
return;
}
final int pivot = values[startIndex+endIndex >> 1];
//loop indices
int i = startIndex, j = endIndex;
// divide into two lists
while(i <= j) {
// scroll forward left list to pivot element
while(values[i] < pivot) i++;
// scroll forward right list to pivot element
while(values[j] > pivot) j--;
/* All values in the left list that are larger then the pivot element and
* all values in the right list that are smaller then the pivot element
* get switched with each other.
*\
if(i <= j) {
final int t = values[i];
values[i] = values[j];
values[j] = t;
i++;
j--;
}
}
// recursion
if(j > startIndex) quicksort(values, startIndex, j);
if(i < endIndex) quicksort(values, i, endIndex);
*/
/**
* Alias for quicksort(values, 0, values.length - 1, comparator)
.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param comparator the comparator to be used to sort the elements
* @throws NullPointerException if the provided array is null
* @see {@link #quicksort(boolean[], int, int, Comparator)}
*/
public static final void quicksort(final boolean[] values) throws NullPointerException
{
quicksort(values, 0, values.length - 1);
}
public static final void quicksort(final boolean[] values, final int startIndex, final int endIndex)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
final boolean pivot = values[startIndex+endIndex >> 1];
int i = startIndex, j = endIndex;
// while(i <= j) {
// while(!values[i] && pivot) i++; //false is smaller than true
// while(values[j] && !pivot) j--; //true is greater then false
// if(i > j) break;
// final boolean t = values[i];
// values[i] = values[j];
// values[j] = t;
// i++;
// j--;
// }
// (30.08.2010 TM)XXX: Test if equivalent (should be)
if(pivot){
while(i <= j) {
while(!values[i]) i++;
if(i > j) break;
final boolean t = values[i];
values[i] = values[j];
values[j] = t;
i++;
j--;
}
}
else {
while(i <= j) {
while(values[j]) j--;
if(i > j) break;
final boolean t = values[i];
values[i] = values[j];
values[j] = t;
i++;
j--;
}
}
if(j > startIndex) quicksort(values, startIndex, j);
if(i < endIndex) quicksort(values, i, endIndex);
}
/**
* Alias for quicksort(values, 0, values.length - 1, comparator)
.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param comparator the comparator to be used to sort the elements
* @throws NullPointerException if the provided array is null
* @see {@link #quicksort(byte[], int, int, Comparator)}
*/
public static final void quicksort(final byte[] values) throws NullPointerException
{
quicksort(values, 0, values.length - 1);
}
public static final void quicksort(final byte[] values, final int startIndex, final int endIndex)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
final byte pivot = values[startIndex+endIndex >> 1]; //use middle element as default pivot
int i = startIndex, j = endIndex;
while(i <= j) {
while(values[i] < pivot) i++;
while(values[j] > pivot) j--;
if(i > j) break;
final byte t = values[i];
values[i] = values[j];
values[j] = t;
i++;
j--;
}
if(j > startIndex) quicksort(values, startIndex, j);
if(i < endIndex) quicksort(values, i, endIndex);
}
/**
* Alias for quicksort(values, 0, values.length - 1, comparator)
.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param comparator the comparator to be used to sort the elements
* @throws NullPointerException if the provided array is null
* @see {@link #quicksort(short[], int, int, Comparator)}
*/
public static final void quicksort(final short[] values) throws NullPointerException
{
quicksort(values, 0, values.length - 1);
}
public static final void quicksort(final short[] values, final int startIndex, final int endIndex)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
final short pivot = values[startIndex+endIndex >> 1];
int i = startIndex, j = endIndex;
while(i <= j) {
while(values[i] < pivot) i++;
while(values[j] > pivot) j--;
if(i > j) break;
final short t = values[i];
values[i] = values[j];
values[j] = t;
i++;
j--;
}
if(j > startIndex) quicksort(values, startIndex, j);
if(i < endIndex) quicksort(values, i, endIndex);
}
/**
* Alias for quicksort(values, 0, values.length - 1, comparator)
.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param comparator the comparator to be used to sort the elements
* @throws NullPointerException if the provided array is null
* @see {@link #quicksort(int[], int, int, Comparator)}
*/
public static final void quicksort(final int[] values) throws NullPointerException
{
quicksort(values, 0, values.length - 1);
}
/**
* (Comparison with JDK implementation showed around 20% better performance of the direct implementation in
* common cases and 60% better performance for bad cases, i.e. presorted list).
*
* @param values
* @param startIndex
* @param endIndex
* @throws NullPointerException
* @throws ArrayIndexOutOfBoundsException
*/
public static final void quicksort(final int[] values, final int startIndex, final int endIndex)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
if(endIndex - startIndex < 20) { //this is actually "length - 1"
for(int i = startIndex; i <= endIndex; i++){
for(int j = i; j > startIndex && values[j-1] > values[j]; j--){
final int t = values[j];
values[j] = values[j-1];
values[j-1] = t;
}
}
return;
}
final int pivot = values[startIndex+endIndex >> 1];
int l = startIndex, r = endIndex;
while(l <= r) {
while(values[l] < pivot) l++;
while(values[r] > pivot) r--;
if(l > r) break;
final int t = values[l];
values[l] = values[r];
values[r] = t;
swapCount++;
l++;
r--;
}
if(r > startIndex) quicksort(values, startIndex, r);
if(l < endIndex) quicksort(values, l, endIndex);
}
public static int swapCount = 0;
/**
* Alias for quicksort(values, 0, values.length - 1, comparator)
.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param comparator the comparator to be used to sort the elements
* @throws NullPointerException if the provided array is null
* @see {@link #quicksort(long[], int, int, Comparator)}
*/
public static final void quicksort(final long[] values) throws NullPointerException
{
quicksort(values, 0, values.length - 1);
}
public static final void quicksort(final long[] values, final int startIndex, final int endIndex)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
final long pivot = values[startIndex+endIndex >> 1];
int i = startIndex, j = endIndex;
while(i <= j) {
while(values[i] < pivot) i++;
while(values[j] > pivot) j--;
if(i > j) break;
final long t = values[i];
values[i] = values[j];
values[j] = t;
i++;
j--;
}
if(j > startIndex) quicksort(values, startIndex, j);
if(i < endIndex) quicksort(values, i, endIndex);
}
/**
* Alias for quicksort(values, 0, values.length - 1, comparator)
.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param comparator the comparator to be used to sort the elements
* @throws NullPointerException if the provided array is null
* @see {@link #quicksort(float[], int, int, Comparator)}
*/
public static final void quicksort(final float[] values) throws NullPointerException
{
quicksort(values, 0, values.length - 1);
}
public static final void quicksort(final float[] values, final int startIndex, final int endIndex)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
final float pivot = values[startIndex+endIndex >> 1];
int i = startIndex, j = endIndex;
while(i <= j) {
while(values[i] < pivot) i++;
while(values[j] > pivot) j--;
if(i > j) break;
final float t = values[i];
values[i] = values[j];
values[j] = t;
i++;
j--;
}
if(j > startIndex) quicksort(values, startIndex, j);
if(i < endIndex) quicksort(values, i, endIndex);
}
/**
* Alias for quicksort(values, 0, values.length - 1, comparator)
.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param comparator the comparator to be used to sort the elements
* @throws NullPointerException if the provided array is null
* @see {@link #quicksort(double[], int, int, Comparator)}
*/
public static final void quicksort(final double[] values) throws NullPointerException
{
quicksort(values, 0, values.length - 1);
}
public static final void quicksort(final double[] values, final int startIndex, final int endIndex)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
final double pivot = values[startIndex+endIndex >> 1];
int i = startIndex, j = endIndex;
while(i <= j) {
while(values[i] < pivot) i++;
while(values[j] > pivot) j--;
if(i > j) break;
final double t = values[i];
values[i] = values[j];
values[j] = t;
i++;
j--;
}
if(j > startIndex) quicksort(values, startIndex, j);
if(i < endIndex) quicksort(values, i, endIndex);
}
/**
* Alias for quicksort(values, 0, values.length - 1, comparator)
.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param comparator the comparator to be used to sort the elements
* @throws NullPointerException if the provided array is null
* @see {@link #quicksort(E[], int, int, Comparator)}
*/
public static final void quicksort(final E[] values, final Comparator comparator) throws NullPointerException
{
quicksort0(values, 0, values.length - 1, comparator);
}
/**
* Direct general purpose sort-unstable implementation of the Quicksort algorithm with insertion sort for arrays
* up to size 8.
*
* The pivot element is always exactely the middle element ((startIndex+endIndex)/2) for the following reasons:
*
* - median3() selection did not yield better performance in practice.
* - using middle element is optimal for presorted lists (see below).
* - using middle element minimizes stacktrace (approx. (8*log(n) - 4) with n being the sort range).
*
*
* Comparison with JDK's Mergesort implementation showed up to 20% better performance
* on randomly filled arrays of length up to 1 million elements (about equal performance on bigger arrays)
* and 300% worse performance on worst cases, i.e. presorted list.
* If presorted lists can happen to be sorted often, Mergesort should be used instead.
*
* Note that no worst-case optimisations like 3-way partitioning is done.
*
* If a special implementation is needed, it should be implemented specifically.
*
* @param the element type
* @param values the array whose elements shall be sorted
* @param startIndex the index of the first element to be sorted
* @param endIndex the index of the last element to be sorted
* @param comparator the comparator to be used to sort the elements.
* @throws NullPointerException if either array or comparator is null
* @throws ArrayIndexOutOfBoundsException if the start and end indices exceed the array bounds.
* @see {@link #quicksort(E[], Comparator)}
*/
public static final void quicksort(final E[] values, final int startIndex, final int endIndex, final Comparator comparator) throws NullPointerException, ArrayIndexOutOfBoundsException
{
if(comparator == null) return;
quicksort0(values, startIndex, endIndex, comparator);
}
private static void quicksort0(final E[] values, final int startIndex, final int endIndex, final Comparator comparator) throws NullPointerException, ArrayIndexOutOfBoundsException
{
//count(new Throwable().getStackTrace().length - 3);
if(endIndex - startIndex < 8) { //this is actually "length - 1", so length 8 and below are insertionsorted
for(int i = startIndex; i <= endIndex; i++){
for(int j = i; j > startIndex && comparator.compare(values[j-1], values[j]) > 0; j--){
final E t = values[j];
values[j] = values[j-1];
values[j-1] = t;
}
}
return;
}
/*
* Note about pivot element selection:
* - values[startIndex] would be most stupid thing to do for presorted arrays
* - values[med3(start, mid, end)] did not yield better performance in any case than just using mid
*
* => so simply use middle element as default pivot.
* yields very good performance in common case and acceptable performance in worst case
* (around 3 times slower than Mergesort for presorted arrays but by no means O(n^2))
*
* Additionally:
* - Using mid index is the optimal pivot element for presorted lists afaik
* - Using mid index minimizes stack depth (around (8*log(n) - 4) in tests. i.e. 53 for 10 mil elements)
*
* If in any event exactely mid element turns out to be bad (e.g. sawtooth data), an alternative could be:
* pivot = values[(startIndex+endIndex >> 1)-4 + random(8)]
*
* Note that the Insertionsort-check above ensures that quicksort range is at least 9.
* So [mid-4;mid+4] will always be safe.
* Maybe random(8) could be performance optimized, like by (System.nanoTime() & 7)
*
* pivot = values[(startIndex+endIndex >> 1)-4 + (int)(System.nanoTime() & 7)] //funny
*/
final E pivot = values[startIndex+endIndex >> 1];
int l = startIndex, r = endIndex;
while(l <= r) {
while(comparator.compare(values[l], pivot) < 0) l++;
while(comparator.compare(values[r], pivot) > 0) r--;
if(l > r) break;
final E t = values[l];
values[l] = values[r];
values[r] = t;
l++;
r--;
}
if(r > startIndex) quicksort0(values, startIndex, r, comparator);
if(l < endIndex) quicksort0(values, l, endIndex, comparator);
}
//only for testing puposes
// private static int med3(final E x[], final int a, final int b, final int c, final Comparator cmp) {
// return cmp.compare(x[a], x[b]) < 0
// ?(cmp.compare(x[b], x[c]) < 0 ? b : cmp.compare(x[a], x[c]) < 0 ? c : a)
// :cmp.compare(x[b], x[c]) > 0 ? b : cmp.compare(x[a], x[c]) > 0 ? c : a
// ;
// }
//only for measuring puposes
// public static HashMap stackStats = new HashMap();
// static void count(final int stackDepth)
// {
// final Integer stackCount = stackStats.get(stackDepth);
// stackStats.put(stackDepth, stackCount == null ?1 :stackCount+1);
// }
///////////////////////////////////////////////////////////////////////////
// Mergesort //
/////////////////////
public static final void mergesort(final E[] values, final Comparator comparator) throws NullPointerException
{
if(comparator == null) return;
// final Object[] aux = new Object[values.length];
// System.arraycopy(values, 0, aux, 0, values.length);
// mergesort0(aux, values, 0, values.length, 0, comparator);
// no idea why, but the very inefficient copyofRange() call is much faster than directly creating the array...
mergesort0(Arrays.copyOfRange(values, 0, values.length), values, 0, values.length, 0, comparator);
}
public static final void mergesort(final E[] values, final int startIndex, final int endIndex, final Comparator comparator)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
if(comparator == null) return;
// final Object[] aux = new Object[values.length];
// System.arraycopy(values, 0, aux, 0, values.length);
// mergesort0(aux, values, startIndex, endIndex, -startIndex, comparator);
// no idea why, but the very inefficient copyofRange() call is much faster than directly creating the array...
mergesort0(
Arrays.copyOfRange(values, startIndex, endIndex+1),
values, startIndex, endIndex+1, -startIndex, comparator
);
}
/*
* Copied from JDK to ensure mergesort
* (JDK's Mergesort in sort() may get replaced by Timsort in Java 7 as it would appear.)
*/
@SuppressWarnings("unchecked")
private static void mergesort0(
final Object[] src,
final Object[] dest,
int low,
int high,
final int off,
final Comparator c
)
throws NullPointerException, ArrayIndexOutOfBoundsException
{
final int length = high - low;
if (length < 7) {
for (int i = low; i < high; i++){
for (int j = i; j > low && c.compare(dest[j-1], dest[j]) > 0; j--){
final Object t = dest[j-1];
dest[j-1] = dest[j];
dest[j] = t;
}
}
return;
}
// Recursively sort halves of dest into src
final int destLow = low;
final int destHigh = high;
low += off;
high += off;
final int mid = low + high >>> 1;
mergesort0(dest, src, low, mid, -off, c);
mergesort0(dest, src, mid, high, -off, c);
/* If list is already sorted, just copy from src to dest.
* This is an optimization that results in faster sorts for nearly ordered lists.
*/
if (c.compare(src[mid-1], src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
// Merge sorted halves (now in src) into dest
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}
}