|
@@ -0,0 +1,1012 @@
|
|
|
+/*
|
|
|
+ * 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 veriables 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 firts 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("Inconsistant comparison function");
|
|
|
+ } 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("Inconsistant comparison function");
|
|
|
+ } 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();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|
|
|
+
|