From af58df2fc03796e6e2a0ddb3812eacf1c2037701 Mon Sep 17 00:00:00 2001 From: "rem..om" Date: Wed, 22 Jan 2014 21:51:42 +0000 Subject: [PATCH] Swich geometry sorting to tim sort. git-svn-id: https://jmonkeyengine.googlecode.com/svn/branches/gradle-restructure@10998 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../com/jme3/renderer/queue/GeometryList.java | 24 +- .../src/main/java/com/jme3/util/ListSort.java | 1012 +++++++++++++++++ 2 files changed, 1021 insertions(+), 15 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/util/ListSort.java diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java index 1016f8bfd..692674291 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java @@ -33,7 +33,7 @@ package com.jme3.renderer.queue; import com.jme3.renderer.Camera; import com.jme3.scene.Geometry; -import com.jme3.util.SortUtil; +import com.jme3.util.ListSort; /** * This class is a special purpose list of {@link Geometry} objects for render @@ -47,8 +47,8 @@ public class GeometryList { private static final int DEFAULT_SIZE = 32; - private Geometry[] geometries; - private Geometry[] geometries2; + private Geometry[] geometries; + private ListSort listSort; private int size; private GeometryComparator comparator; @@ -60,9 +60,9 @@ public class GeometryList { */ public GeometryList(GeometryComparator comparator) { size = 0; - geometries = new Geometry[DEFAULT_SIZE]; - geometries2 = new Geometry[DEFAULT_SIZE]; + geometries = new Geometry[DEFAULT_SIZE]; this.comparator = comparator; + listSort = new ListSort(); } public void setComparator(GeometryComparator comparator) { @@ -118,8 +118,6 @@ public class GeometryList { Geometry[] temp = new Geometry[size * 2]; System.arraycopy(geometries, 0, temp, 0, size); geometries = temp; // original list replaced by double-size list - - geometries2 = new Geometry[size * 2]; } geometries[size++] = g; } @@ -141,14 +139,10 @@ public class GeometryList { public void sort() { if (size > 1) { // sort the spatial list using the comparator - -// SortUtil.qsort(geometries, 0, size, comparator); -// Arrays.sort(geometries, 0, size, comparator); - - System.arraycopy(geometries, 0, geometries2, 0, size); - SortUtil.msort(geometries2, geometries, 0, size-1, comparator); - - + if(listSort.getLength() != size){ + listSort.allocateStack(size); + } + listSort.sort(geometries,comparator); } } } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/ListSort.java b/jme3-core/src/main/java/com/jme3/util/ListSort.java new file mode 100644 index 000000000..5d3bbc6fc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/ListSort.java @@ -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 { + + /** + * 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 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 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 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 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 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 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 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 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 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 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() { + 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(); + } + + + +} +