/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.util;
import java.util.Comparator;
/**
* Fast, stable sort used to sort geometries
*
* It's adapted from Tim Peters's work on list sorting for Python. More details
* here http://svn.python.org/projects/python/trunk/Objects/listsort.txt
*
* here is the C code from which this class is based
* http://svn.python.org/projects/python/trunk/Objects/listobject.c
*
* This class was also greatly inspired from java 7 TimSort by Josh Blosh with the
* difference that the temporary necessary memory space are allocated as the
* geometry list grows and reused all along the application execution.
*
* Usage : ListSort has to be instanciated and kept with the geometry list ( or
* w/e it may have to sort) Then the allocate method has to be called to
* allocate necessary tmp space needed for the sort. This should be called once
* for optimal performance, but can be called several times if the length of the
* list changes
*
* Disclaimer : I was intrigued by the use of val >>> 1 in java 7 Timsort class
* instead of val / 2 (integer division). Micro benching revealed that val >>> 1
* is twice faster than val / 2 in java 6 and has similar perf in java 7. The
* following code uses val >>> 1 when ever a value needs to be divided by 2 and
* rounded to its floor
*
*
* @author Nehon
*/
public class ListSort<T> {
/**
* Threshold for binary sort vs merge. Original algorithm use 64, java7
* TimSort uses 32 and I used 128, see this post for explanations :
* http://hub.jmonkeyengine.org/groups/development-discussion-jme3/forum/topic/i-got-that-sorted-out-huhuhu/
*/
private static final int MIN_SIZE = 128;
private T[] array;
private T[] tmpArray;
private Comparator<T> comparator;
/**
* attribute temp vars for merging. This was used to unroll the merge_lo &
* merge_hi function of original implementations that used massive labeled
* goto branching and was almost unreadable
*/
int iterA, iterB, dest, lengthA, lengthB;
/**
* Number of runs to merge
*/
private int nbRuns = 0;
/* Try to used a kind of structure like in the original implementation.
* Ended up using 2 arrays as done in the java 7 Timsort.
* Original implementation use a struct, but instanciation of this inner
* class + array was a convoluted pain.
*/
/**
* array of start indices in the original array for runs : run i sarting
* index is at runIndices[i]
*/
private int[] runsIndices = null;
/**
* array of runs length in the original array : run i length is at
* runLength[i]
*/
private int[] runsLength = null;
/**
* Length of the array to sort.(the passed on array is allocated by chunks
* of 32, so its length may be bigger than the actual useful data to sort)
*/
private int length = 0;
/**
* MIN_GALLOP set to 7 constant as described in listsort.txt. this magic
* number indicates how many wins should trigger the switch from binary
* search to gallopping mode
*/
private static final int MIN_GALLOP = 7;
/**
* This variable allows to adjust when switching to galloping mode. lowered
* when the data are "naturally" structured highered when data are random.
*/
private int minGallop = MIN_GALLOP;
/**
* Creates a ListSort
*/
public ListSort() {
}
/**
* Allocate temp variables for the given length This method should be called
* at least once, but only if the length of the list to sort changed before
* sorting
*
* @param len
*/
public final void allocateStack(int len) {
length = len;
/*
* We allocate a temp array of half the size of the array to sort.
* the original implementation had a 256 maximum size for this and made
* the temp array grow on demand
*
* Timsort consumes half the size of the original array to merge at WORST.
* But considering we use the same temp array over and over across frames
* There is a good chance we stumble upon the worst case scenario one
* moment or another.
* So we just always take half of the original array size.
*/
int tmpLen = len >>> 1;
//if the array is null or tmpLen is above the actual length we allocate the array
if (tmpArray == null || tmpLen > tmpArray.length) {
//has to use Object for temp storage
tmpArray = (T[]) new Object[tmpLen];
}
/*
* this part was taken from java 7 TimSort.
* The original implementation use a stack of length 85, but this seems
* to boost up performance for mid sized arrays.
* I changed the numbers so they fit our MIN_SIZE parameter.
*
* Those numbers can be computed using this formula :
* MIN_SIZE * 1.618^n = N
* Where n is the size of the stack, and N the number element of the array to sort
* If MIN_SIZE is changed you have to recompute those values.
*/
int stackLen = (len < 1400 ? 5
: len < 15730 ? 10
: len < 1196194 ? 19 : 40);
//Same remark as with the temp array
if (runsIndices == null || stackLen > runsIndices.length) {
runsIndices = new int[stackLen];
runsLength = new int[stackLen];
}
}
/**
* reset the runs stack to 0
*/
private void clean() {
for (int i = 0; i < runsIndices.length; i++) {
runsIndices[i] = 0;
runsLength[i] = 0;
}
}
/**
* Sort the given array given the comparator
* @param array the array to sort
* @param comparator the comparator to compare elements of the array
*/
public void sort(T[] array, Comparator<T> comparator) {
this.array = array;
this.comparator = comparator;
clean();
int low = 0;
int high = length;
int remaining = high - low;
/*
* If array's size is bellow min_size we perform a binary insertion sort
* but first we check if some existing ordered pattern exists to reduce
* the size of data to be sorted
*/
if (remaining < MIN_SIZE) {
int runLength = getRunLength(array, low, high, comparator);
binaryInsertionSort(array, low, high, low + runLength, comparator);
return;
}
/*
* main iteration : compute minrun length, then iterate through the
* array to find runs and merge them until they can be binary sorted
* if their length < minLength
*/
int minLength = mergeComputeMinRun(remaining);
while (remaining != 0) {
int runLength = getRunLength(array, low, high, comparator);
/* if runlength is bellow the threshold we binary sort the remaining
* elements
*/
if (runLength < minLength) {
int newLength = remaining <= minLength ? remaining : minLength;
binaryInsertionSort(array, low, low + newLength, low + runLength, comparator);
runLength = newLength;
}
// Add run to pending runs to merge and merge if necessary.
runsIndices[nbRuns] = low;
runsLength[nbRuns] = runLength;
nbRuns++;
mergeCollapse();
// Advance to find next run
low += runLength;
remaining -= runLength;
}
// Merge all remaining runs to complete sort
mergeForceCollapse();
}
/**
* Return the length of the run beginning at lastId, in the slice [lastId,
* lastId]. firstId < lastId is required on entry. "A run" is the longest
* ascending sequence, with
*
* array[0] <= array[1] <= array[2] <= ...
*
* or the longest descending sequence, with
*
* array[0] > array[1] > array[2] > ...
*
* The original algorithm is returning a "descending" boolean that allow the
* caller to reverse the array. Here for simplicity we reverse the array
* when the run is descending
*
* @param array the array to search for run length
* @param firstId index of the first element of the run
* @param lastId index+1 of the last element of the run
* @param comparator the comparator
* @return the length of the run beginning at the specified position in the
* specified array
*/
private int getRunLength(T[] array, int firstId, int lastId,
Comparator<T> comparator) {
int runEnd = firstId + 1;
if (runEnd < lastId) {
// if the range is > 1 we search for the end index of the run
if (comparator.compare(array[runEnd++], array[firstId]) >= 0) {
while (runEnd < lastId && comparator.compare(array[runEnd], array[runEnd - 1]) >= 0) {
runEnd++;
}
} else {
while (runEnd < lastId && comparator.compare(array[runEnd], array[runEnd - 1]) < 0) {
runEnd++;
}
// the run's order is descending, it has to be reversed
// original algorithmm return a descending = 1 value and the
//reverse is done in the sort method. Looks good to have it here though
reverseArray(array, firstId, runEnd);
}
return runEnd - firstId;
}
//runEnd == lastId -> length = 1
return 1;
}
/**
* binarysort is the best method for sorting small arrays: it does few
* compares, but can do data movement quadratic in the number of elements.
* [firstId, lastId] is a contiguous slice of a list, and is sorted via
* binary insertion. This sort is stable. On entry, must have firstId <=
* start <= lastId, and that [firstId, start) is already sorted (pass start
* == firstId if you don't know!).
*
* @param array the array to sort
* @param firstId the index of the first element to sort
* @param lastId the index+ of the last element to sort
* @param start the index of the element to start sorting range
* [firstId,satrt]is assumed to be already sorted
* @param comparator the comparator
*/
private void binaryInsertionSort(T[] array, int firstId, int lastId, int start,
Comparator<T> comparator) {
if (firstId == start) {
start++;
}
while (start < lastId) {
T pivot = array[start];
// set left to where start belongs
int left = firstId;
int right = start;
/* Invariants:
* pivot >= all in [firstId, left).
* pivot < all in [right, start).
* The second is vacuously true at the start.
*/
while (left < right) {
int middle = (left + right) >>> 1;
if (comparator.compare(pivot, array[middle]) < 0) {
right = middle;
} else {
left = middle + 1;
}
}
/*
* The invariants still hold, so pivot >= all in [firstId, left) and
* pivot < all in [left, start), so pivot belongs at left. Note
* that if there are elements equal to pivot, left points to the
* first slot after them -- that's why this sort is stable.
* Slide over to make room.
*/
int nbElems = start - left;
/*
* grabbed from java7 TimSort, the swich is an optimization to
* arraycopy in case there are 1 or 2 elements only to copy
*/
switch (nbElems) {
case 2:
array[left + 2] = array[left + 1];
case 1:
array[left + 1] = array[left];
break;
default:
System.arraycopy(array, left, array, left + 1, nbElems);
}
array[left] = pivot;
start++;
}
}
/**
* returns the minimum run length for merging
*
* see http://svn.python.org/projects/python/trunk/Objects/listobject.c
* almost exact copy of merge_compute_minrun function
*
* If n < MIN_SIZE, return n (it's too small to bother with fancy stuff).
* Else if n is an exact power of 2, return MIN_SIZE / 2. Else return an int
* k, MIN_SIZE / 2 <= k <= MIN_SIZE , such that n/k is close to, but
* strictly less than, an exact power of 2.
*
* @param n length of the array
* @return the minimum run length for
*/
private int mergeComputeMinRun(int n) {
int r = 0; /* becomes 1 if any 1 bits are shifted off */
while (n >= MIN_SIZE) {
r |= (n & 1);
n >>= 1;
}
return n + r;
}
/**
* Examine the stack of runs waiting to be merged, merging adjacent runs
* until the stack invariants are re-established:
*
* 1. len[-3] > len[-2] + len[-1] 2. len[-2] > len[-1]
*
* See http://svn.python.org/projects/python/trunk/Objects/listobject.c very
* similar to merge_collapse
*
* see http://svn.python.org/projects/python/trunk/Objects/listsort.txt
* search for The Merge Pattern
*/
private void mergeCollapse() {
while (nbRuns > 1) {
int n = nbRuns - 2;
//searching for runs to merge from the end of the stack
if (n > 0 && runsLength[n - 1] <= runsLength[n] + runsLength[n + 1]) {
if (runsLength[n - 1] < runsLength[n + 1]) {
n--;
}
mergeRuns(n);
} else if (runsLength[n] <= runsLength[n + 1]) {
mergeRuns(n);
} else {
break;
}
}
}
/**
* Merge all the remaining runs to merge
*/
private void mergeForceCollapse() {
while (nbRuns > 1) {
int n = nbRuns - 2;
if (n > 0 && runsLength[n - 1] < runsLength[n + 1]) {
n--;
}
mergeRuns(n);
}
}
/**
* Merge runs A and B where A index in the stack is idx and B index is idx+1
*
* @param idx index of the first of two runs to merge
*/
private void mergeRuns(int idx) {
int indexA = runsIndices[idx];
int lenA = runsLength[idx];
int indexB = runsIndices[idx + 1];
int lenB = runsLength[idx + 1];
/*
* Record the length of the combined runs; if idx is the 3rd-last
* run now, also slide over the last run (which isn't involved
* in this merge). The current run (idx+1) goes away in any case.
*/
runsLength[idx] = lenA + lenB;
if (idx == nbRuns - 3) {
runsIndices[idx + 1] = runsIndices[idx + 2];
runsLength[idx + 1] = runsLength[idx + 2];
}
nbRuns--;
/* Where does B start in A? Elements in A before that can be
* ignored (already in place).
*/
//didn't find proper naming for k as it's used inthe original implementation
int k = gallopRight(array[indexB], array, indexA, lenA, 0, comparator);
indexA += k;
lenA -= k;
if (lenA == 0) {
return;
}
/* Where does A end in B? Elements in B after that can be
* ignored (already in place).
*/
lenB = gallopLeft(array[indexA + lenA - 1], array, indexB, lenB, lenB - 1, comparator);
if (lenB == 0) {
return;
}
/* Merge what remains of the runs, using a temp array with
* min(lengthA, lengthB) elements.
*/
if (lenA <= lenB) {
mergeLow(indexA, lenA, indexB, lenB);
} else {
mergeHigh(indexA, lenA, indexB, lenB);
}
}
/**
*
* Locate the proper position of key in an array; if the array contains an
* element equal to key, return the position immediately to the left of the
* leftmost equal element. [gallopRight() does the same except returns the
* position to the right of the rightmost equal element (if any).]
*
* @param key the key to search
* @param array is a sorted array with n elements, starting at array[0]. n
* must be > 0.
* @param idx the index to start
* @param length the length of the run
* @param hint is an index at which to begin the search, 0 <= hint < n. The
* closer hint is to the final result, the faster this runs.
* @param comparator the comparator used to order the range, and to search
* @return is the int k in 0..n such that
*
* array[k-1] < key <= array[k]
*
* pretending that *(a-1) is minus infinity and array[n] is plus infinity.
* IOW, key belongs at index k; or, IOW, the first k elements of a should
* precede key, and the last n-k should follow key.
*/
private int gallopLeft(T key, T[] array, int idx, int length, int hint,
Comparator<T> comparator) {
int lastOffset = 0;
int offset = 1;
if (comparator.compare(key, array[idx + hint]) > 0) {
/* array[hint] < key -- gallop right, until
* array[hint + lastOffset] < key <= array[hint + offset]
*/
int maxOffset = length - hint;
while (offset < maxOffset && comparator.compare(key, array[idx + hint + offset]) > 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
/* int overflow.
* Note : not sure if that can happen but it's here in both
* original and java 7 TimSort implementation
*/
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
// Translate back to offsets relative to idx.
lastOffset += hint;
offset += hint;
} else {
/* key <= array[hint] -- gallop left, until
* array[hint - offset] < key <= array[hint - lastOffset]
*/
int maxOffset = hint + 1;
while (offset < maxOffset && comparator.compare(key, array[idx + hint - offset]) <= 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
/* int overflow.
* Note : not sure if that can happen but it's here in both
* original and java 7 TimSort implementation
*/
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
// Translate back to positive offsets relative to idx.
int k = lastOffset;
lastOffset = hint - offset;
offset = hint - k;
}
/*
* Now array[idx+lastOffset] < key <= array[idx+offset], so key belongs somewhere
* to the right of lastOffset but no farther right than offset. Do a binary
* search, with invariant array[idx + lastOffset - 1] < key <= array[idx + offset].
*/
lastOffset++;
while (lastOffset < offset) {
int m = lastOffset + ((offset - lastOffset) >>> 1);
if (comparator.compare(key, array[idx + m]) > 0) {
lastOffset = m + 1; // array[idx + m] < key
} else {
offset = m; // key <= array[idx + m]
}
}
return offset;
}
/**
* Exactly like gallopLeft(), except that if key already exists in
* array[0:n], finds the position immediately to the right of the rightmost
* equal value.
*
* The code duplication is massive, but this is enough different given that
* we're sticking to "<" comparisons that it's much harder to follow if
* written as one routine with yet another "left or right?" flag.
*
* @param key the key to search
* @param array is a sorted array with n elements, starting at array[0]. n
* must be > 0.
* @param idx the index to start
* @param length the length of the run
* @param hint is an index at which to begin the search, 0 <= hint < n. The
* closer hint is to the final result, the faster this runs.
* @param comparator the comparator used to order the range, and to search
* @return value is the int k in 0..n such that array[k-1] <= key < array[k]
*/
private int gallopRight(T key, T[] array, int idx, int length,
int hint, Comparator<T> comparator) {
int offset = 1;
int lastOffset = 0;
if (comparator.compare(key, array[idx + hint]) < 0) {
/* key < array[hint] -- gallop left, until
* array[hint - offset] <= key < array[hint - lastOffset]
*/
int maxOffset = hint + 1;
while (offset < maxOffset && comparator.compare(key, array[idx + hint - offset]) < 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
/* int overflow.
* Note : not sure if that can happen but it's here in both
* original and java 7 TimSort implementation
*/
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
// Translate back to offsets relative to idx.
int k = lastOffset;
lastOffset = hint - offset;
offset = hint - k;
} else {
/* array[hint] <= key -- gallop right, until
* array[hint + lastOffset] <= key < array[hint + offset]
*/
int maxOffset = length - hint;
while (offset < maxOffset && comparator.compare(key, array[idx + hint + offset]) >= 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
/* int overflow.
* Note : not sure if that can happen but it's here in both
* original and java 7 TimSort implementation
*/
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
// Translate back to offsets relative to idx.
lastOffset += hint;
offset += hint;
}
/* Now array[lastOffset] <= key < array[offset], so key belongs somewhere to the
* right of lastOffset but no farther right than offset. Do a binary
* search, with invariant array[lastOffset-1] <= key < array[offset].
*/
lastOffset++;
while (lastOffset < offset) {
int m = lastOffset + ((offset - lastOffset) >>> 1);
if (comparator.compare(key, array[idx + m]) < 0) {
offset = m; //key < array[idx + m]
} else {
lastOffset = m + 1; // array[idx + m] <= key
}
}
return offset;
}
/**
* Merge the lenA elements starting at idxA with the lenB elements starting
* at idxB in a stable way, in-place. lenA and lenB must be > 0, and idxA +
* lenA = idxB Must also have that array[idxB] < array[idxA], that
* array[idxA+lenA - 1] belongs at the end of the merge, and should have
* lenA <= lenB. See listsort.txt for more info.
*
* @param idxA index of first element in run A
* @param lengthA length of run A
* @param idxB index of first element in run B
* @param lengthB length of run B
*/
private void mergeLow(int idxA, int lenA, int idxB, int lenB) {
lengthA = lenA;
lengthB = lenB;
iterA = 0; // Indexes into tmp array
iterB = idxB; // Indexes int a
dest = idxA; // Indexes int a
Comparator<T> comp = this.comparator;
T[] arr = this.array;
T[] tempArray = tmpArray;
System.arraycopy(arr, idxA, tempArray, 0, lengthA);
arr[dest] = arr[iterB];
dest++;
iterB++;
innerMergeLow(comp, arr, tempArray);
//minGallop shouldn't be < 1
minGallop = minGallop < 1 ? 1 : minGallop;
if (lengthA == 1) {//CopyB label
System.arraycopy(arr, iterB, arr, dest, lengthB);
// The last element of run A belongs at the end of the merge.
arr[dest + lengthB] = tempArray[iterA];
} else if(lengthA == 0){
throw new UnsupportedOperationException("Compare function result changed! " +
"Make sure you do not modify the scene from"
+ " another thread and that the comparisons are not based"
+ " on NaN values.");
} else {//Fail label
System.arraycopy(tempArray, iterA, arr, dest, lengthA);
}
}
/**
* Attempt to unroll "goto" style original implementation.
* this method uses and change temp attributes of the class
* @param comp comparator
* @param arr the array
* @param tempArray the temp array
*/
public void innerMergeLow(Comparator<T> comp, T[] arr, T[] tempArray) {
lengthB--;
if (lengthB == 0 || lengthA == 1) {
return;
}
while (true) {
// Number of wins by run A
int aWins = 0;
// Number of wins by run B
int bWins = 0;
/*
* Do the straightforward thing until (if ever) one run starts
* winning consistently.
*/
do {
if (comp.compare(arr[iterB], tempArray[iterA]) < 0) {
arr[dest] = arr[iterB];
dest++;
iterB++;
bWins++;
aWins = 0;
lengthB--;
if (lengthB == 0) {
return;
}
} else {
arr[dest] = tempArray[iterA];
dest++;
iterA++;
aWins++;
bWins = 0;
lengthA--;
if (lengthA == 1) {
return;
}
}
} while ((aWins | bWins) < minGallop);
/*
* One run is winning so consistently that galloping may be a
* huge win. So try that, and continue galloping until (if ever)
* neither run appears to be winning consistently anymore.
*/
do {
aWins = gallopRight(arr[iterB], tempArray, iterA, lengthA, 0, comp);
if (aWins != 0) {
System.arraycopy(tempArray, iterA, arr, dest, aWins);
dest += aWins;
iterA += aWins;
lengthA -= aWins;
/* lengthA==0 is impossible now if the comparison
* function is consistent, but we can't assume
* that it is.
* a propper error will be thrown in mergeLow if lengthA == 0
*/
if (lengthA <= 1){
return;
}
}
arr[dest] = arr[iterB];
dest++;
iterB++;
lengthB--;
if (lengthB == 0) {
return;
}
bWins = gallopLeft(tempArray[iterA], arr, iterB, lengthB, 0, comp);
if (bWins != 0) {
System.arraycopy(arr, iterB, arr, dest, bWins);
dest += bWins;
iterB += bWins;
lengthB -= bWins;
if (lengthB == 0) {
return;
}
}
arr[dest] = tempArray[iterA];
dest++;
iterA++;
lengthA--;
if (lengthA == 1) {
return;
}
minGallop--;
} while (aWins >= MIN_GALLOP || bWins >= MIN_GALLOP);
if (minGallop < 0) {
minGallop = 0;
}
//original implementation uses +1 to penalize, Java7 Timsort uses +2
minGallop += 2; // Penalize for leaving gallop mode
}
}
/**
* Merge the lenA elements starting at idxA with the lenB elements starting
* at idxB in a stable way, in-place. lenA and lenBb must be > 0, and idxA +
* lenAa == idxB. Must also have that array[idxB] < array[idxA], that
* array[idxA + Len1 - 1] belongs at the end of the merge, and should have
* lenA >= lenB. See listsort.txt for more info.
*
* @param idxA index of first element in run A
* @param lengthA length of run A
* @param idxB index of first element in run B
* @param lengthB length of run B
*/
private void mergeHigh(int idxA, int lenA, int idxB, int lenB) {
lengthA = lenA;
lengthB = lenB;
iterA = idxA + lengthA - 1;
iterB = lengthB - 1;
dest = idxB + lengthB - 1;
Comparator<T> comp = this.comparator;
T[] arr = this.array;
T[] tempArray = tmpArray;
System.arraycopy(arr, idxB, tempArray, 0, lengthB);
arr[dest] = arr[iterA];
dest--;
iterA--;
innerMergeHigh(comp, tempArray, arr, idxA);
//minGallop shouldn't be < 1;
minGallop = minGallop < 1 ? 1 : minGallop;
if (lengthB == 1) {//CopyA label
dest -= lengthA;
iterA -= lengthA;
System.arraycopy(arr, iterA + 1, arr, dest + 1, lengthA);
// The first element of run B belongs at the front of the merge.
arr[dest] = tempArray[iterB];
} else if (lengthB == 0) {
throw new UnsupportedOperationException("Compare function result changed! " +
"Make sure you do not modify the scene from another thread!");
} else {//Fail label
System.arraycopy(tempArray, 0, arr, dest - (lengthB - 1), lengthB);
}
}
/**
* Attempt to unroll "goto" style original implementation.
* this method uses and change temp attributes of the class
* @param comp comparator
* @param arr the array
* @param tempArray the temp array
* @param idxA the index of the first element of run A
*/
public void innerMergeHigh(Comparator<T> comp, T[] tempArray, T[] arr, int idxA) {
lengthA--;
if (lengthA == 0 || lengthB == 1) {
return;
}
if (lengthB == 1) {
return;
}
while (true) {
// Number of wins by run A
int aWins = 0;
// Number of wins by run B
int bWins = 0;
/*
* Do the straightforward thing until (if ever) one run
* appears to win consistently.
*/
do {
if (comp.compare(tempArray[iterB], arr[iterA]) < 0) {
arr[dest] = arr[iterA];
dest--;
iterA--;
aWins++;
bWins = 0;
lengthA --;
if (lengthA == 0) {
return;
}
} else {
arr[dest] = tempArray[iterB];
dest--;
iterB--;
bWins++;
aWins = 0;
lengthB--;
if (lengthB == 1) {
return;
}
}
} while ((aWins | bWins) < minGallop);
/*
* One run is winning so consistently that galloping may be a
* huge win. So try that, and continue galloping until (if ever)
* neither run appears to be winning consistently anymore.
*/
do {
aWins = lengthA - gallopRight(tempArray[iterB], arr, idxA, lengthA, lengthA - 1, comp);
if (aWins != 0) {
dest -= aWins;
iterA -= aWins;
lengthA -= aWins;
System.arraycopy(arr, iterA + 1, arr, dest + 1, aWins);
if (lengthA == 0) {
return;
}
}
arr[dest] = tempArray[iterB];
dest--;
iterB--;
lengthB--;
if (lengthB == 1) {
return;
}
bWins = lengthB - gallopLeft(arr[iterA], tempArray, 0, lengthB, lengthB - 1, comp);
if (bWins != 0) {
dest -= bWins;
iterB -= bWins;
lengthB -= bWins;
System.arraycopy(tempArray, iterB + 1, arr, dest + 1, bWins);
/* lengthB==0 is impossible now if the comparison
* function is consistent, but we can't assume
* that it is.
* a propper error will be thrown in mergeLow if lengthB == 0
*/
if (lengthB <= 1){
return;
}
}
arr[dest] = arr[iterA];
dest--;
iterA--;
lengthA--;
if (lengthA == 0) {
return;
}
minGallop--;
} while (aWins >= MIN_GALLOP || bWins >= MIN_GALLOP);
if (minGallop < 0) {
minGallop = 0;
}
//original implementation uses +1 to penalize, Java7 Timsort uses +2
minGallop += 2; // Penalize for leaving gallop mode
}
}
/**
* Reverse an array from firstId to lastId
*
* @param array the array to reverse
* @param firstId the index where to start to reverse
* @param lastId the index where to stop to reverse
*/
private static void reverseArray(Object[] array, int firstId, int lastId) {
lastId--;
while (firstId < lastId) {
Object o = array[firstId];
array[firstId] = array[lastId];
array[lastId] = o;
firstId++;
lastId--;
}
}
/**
* return the useful length of the array being sorted
* @return the length pass to the last allocateStack method
*/
public int getLength() {
return length;
}
/*
* test case
*/
public static void main(String[] argv) {
Integer[] arr = new Integer[]{5, 6, 2, 9, 10, 11, 12, 8, 3, 12, 3, 7, 12, 32, 458, 12, 5, 3, 78, 45, 12, 32, 58, 45, 65, 45, 98, 45, 65, 2, 3, 47, 21, 35};
ListSort ls = new ListSort();
ls.allocateStack(34);
ls.sort(arr, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
int x = o1 - o2;
return (x == 0) ? 0 : (x > 0) ? 1 : -1;
}
});
for (Integer integer : arr) {
System.err.print(integer + ",");
}
System.err.println();
}
}