blob: d36b648c0456d51023bf1c316dcc138d0106a126 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.util;
import java.util.Arrays;
/**
* {@link Sorter} implementation based on the
* <a href="http://svn.python.org/projects/python/trunk/Objects/listsort.txt">TimSort</a>
* algorithm.
* <p>This implementation is especially good at sorting partially-sorted
* arrays and sorts small arrays with binary sort.
* <p><b>NOTE</b>:There are a few differences with the original implementation:<ul>
* <li><a name="maxTempSlots"></a>The extra amount of memory to perform merges is
* configurable. This allows small merges to be very fast while large merges
* will be performed in-place (slightly slower). You can make sure that the
* fast merge routine will always be used by having <code>maxTempSlots</code>
* equal to half of the length of the slice of data to sort.
* <li>Only the fast merge routine can gallop (the one that doesn't run
* in-place) and it only gallops on the longest slice.
* </ul>
* @lucene.internal
*/
public abstract class TimSorter extends Sorter {
static final int MINRUN = 32;
static final int THRESHOLD = 64;
static final int STACKSIZE = 49; // depends on MINRUN
static final int MIN_GALLOP = 7;
final int maxTempSlots;
int minRun;
int to;
int stackSize;
int[] runEnds;
/**
* Create a new {@link TimSorter}.
* @param maxTempSlots the <a href="#maxTempSlots">maximum amount of extra memory to run merges</a>
*/
protected TimSorter(int maxTempSlots) {
super();
runEnds = new int[1 + STACKSIZE];
this.maxTempSlots = maxTempSlots;
}
/** Minimum run length for an array of length <code>length</code>. */
static int minRun(int length) {
assert length >= MINRUN;
int n = length;
int r = 0;
while (n >= 64) {
r |= n & 1;
n >>>= 1;
}
final int minRun = n + r;
assert minRun >= MINRUN && minRun <= THRESHOLD;
return minRun;
}
int runLen(int i) {
final int off = stackSize - i;
return runEnds[off] - runEnds[off - 1];
}
int runBase(int i) {
return runEnds[stackSize - i - 1];
}
int runEnd(int i) {
return runEnds[stackSize - i];
}
void setRunEnd(int i, int runEnd) {
runEnds[stackSize - i] = runEnd;
}
void pushRunLen(int len) {
runEnds[stackSize + 1] = runEnds[stackSize] + len;
++stackSize;
}
/** Compute the length of the next run, make the run sorted and return its
* length. */
int nextRun() {
final int runBase = runEnd(0);
assert runBase < to;
if (runBase == to - 1) {
return 1;
}
int o = runBase + 2;
if (compare(runBase, runBase+1) > 0) {
// run must be strictly descending
while (o < to && compare(o - 1, o) > 0) {
++o;
}
reverse(runBase, o);
} else {
// run must be non-descending
while (o < to && compare(o - 1, o) <= 0) {
++o;
}
}
final int runHi = Math.max(o, Math.min(to, runBase + minRun));
binarySort(runBase, runHi, o);
return runHi - runBase;
}
void ensureInvariants() {
while (stackSize > 1) {
final int runLen0 = runLen(0);
final int runLen1 = runLen(1);
if (stackSize > 2) {
final int runLen2 = runLen(2);
if (runLen2 <= runLen1 + runLen0) {
// merge the smaller of 0 and 2 with 1
if (runLen2 < runLen0) {
mergeAt(1);
} else {
mergeAt(0);
}
continue;
}
}
if (runLen1 <= runLen0) {
mergeAt(0);
continue;
}
break;
}
}
void exhaustStack() {
while (stackSize > 1) {
mergeAt(0);
}
}
void reset(int from, int to) {
stackSize = 0;
Arrays.fill(runEnds, 0);
runEnds[0] = from;
this.to = to;
final int length = to - from;
this.minRun = length <= THRESHOLD ? length : minRun(length);
}
void mergeAt(int n) {
assert stackSize >= 2;
merge(runBase(n + 1), runBase(n), runEnd(n));
for (int j = n + 1; j > 0; --j) {
setRunEnd(j, runEnd(j-1));
}
--stackSize;
}
void merge(int lo, int mid, int hi) {
if (compare(mid - 1, mid) <= 0) {
return;
}
lo = upper2(lo, mid, mid);
hi = lower2(mid, hi, mid - 1);
if (hi - mid <= mid - lo && hi - mid <= maxTempSlots) {
mergeHi(lo, mid, hi);
} else if (mid - lo <= maxTempSlots) {
mergeLo(lo, mid, hi);
} else {
mergeInPlace(lo, mid, hi);
}
}
@Override
public void sort(int from, int to) {
checkRange(from, to);
if (to - from <= 1) {
return;
}
reset(from, to);
do {
ensureInvariants();
pushRunLen(nextRun());
} while (runEnd(0) < to);
exhaustStack();
assert runEnd(0) == to;
}
@Override
void doRotate(int lo, int mid, int hi) {
final int len1 = mid - lo;
final int len2 = hi - mid;
if (len1 == len2) {
while (mid < hi) {
swap(lo++, mid++);
}
} else if (len2 < len1 && len2 <= maxTempSlots) {
save(mid, len2);
for (int i = lo + len1 - 1, j = hi - 1; i >= lo; --i, --j) {
copy(i, j);
}
for (int i = 0, j = lo; i < len2; ++i, ++j) {
restore(i, j);
}
} else if (len1 <= maxTempSlots) {
save(lo, len1);
for (int i = mid, j = lo; i < hi; ++i, ++j) {
copy(i, j);
}
for (int i = 0, j = lo + len2; j < hi; ++i, ++j) {
restore(i, j);
}
} else {
reverse(lo, mid);
reverse(mid, hi);
reverse(lo, hi);
}
}
void mergeLo(int lo, int mid, int hi) {
assert compare(lo, mid) > 0;
int len1 = mid - lo;
save(lo, len1);
copy(mid, lo);
int i = 0, j = mid + 1, dest = lo + 1;
outer: for (;;) {
for (int count = 0; count < MIN_GALLOP; ) {
if (i >= len1 || j >= hi) {
break outer;
} else if (compareSaved(i, j) <= 0) {
restore(i++, dest++);
count = 0;
} else {
copy(j++, dest++);
++count;
}
}
// galloping...
int next = lowerSaved3(j, hi, i);
for (; j < next; ++dest) {
copy(j++, dest);
}
restore(i++, dest++);
}
for (; i < len1; ++dest) {
restore(i++, dest);
}
assert j == dest;
}
void mergeHi(int lo, int mid, int hi) {
assert compare(mid - 1, hi - 1) > 0;
int len2 = hi - mid;
save(mid, len2);
copy(mid - 1, hi - 1);
int i = mid - 2, j = len2 - 1, dest = hi - 2;
outer: for (;;) {
for (int count = 0; count < MIN_GALLOP; ) {
if (i < lo || j < 0) {
break outer;
} else if (compareSaved(j, i) >= 0) {
restore(j--, dest--);
count = 0;
} else {
copy(i--, dest--);
++count;
}
}
// galloping
int next = upperSaved3(lo, i + 1, j);
while (i >= next) {
copy(i--, dest--);
}
restore(j--, dest--);
}
for (; j >= 0; --dest) {
restore(j--, dest);
}
assert i == dest;
}
int lowerSaved(int from, int to, int val) {
int len = to - from;
while (len > 0) {
final int half = len >>> 1;
final int mid = from + half;
if (compareSaved(val, mid) > 0) {
from = mid + 1;
len = len - half -1;
} else {
len = half;
}
}
return from;
}
int upperSaved(int from, int to, int val) {
int len = to - from;
while (len > 0) {
final int half = len >>> 1;
final int mid = from + half;
if (compareSaved(val, mid) < 0) {
len = half;
} else {
from = mid + 1;
len = len - half -1;
}
}
return from;
}
// faster than lowerSaved when val is at the beginning of [from:to[
int lowerSaved3(int from, int to, int val) {
int f = from, t = f + 1;
while (t < to) {
if (compareSaved(val, t) <= 0) {
return lowerSaved(f, t, val);
}
int delta = t - f;
f = t;
t += delta << 1;
}
return lowerSaved(f, to, val);
}
//faster than upperSaved when val is at the end of [from:to[
int upperSaved3(int from, int to, int val) {
int f = to - 1, t = to;
while (f > from) {
if (compareSaved(val, f) >= 0) {
return upperSaved(f, t, val);
}
final int delta = t - f;
t = f;
f -= delta << 1;
}
return upperSaved(from, t, val);
}
/** Copy data from slot <code>src</code> to slot <code>dest</code>. */
protected abstract void copy(int src, int dest);
/** Save all elements between slots <code>i</code> and <code>i+len</code>
* into the temporary storage. */
protected abstract void save(int i, int len);
/** Restore element <code>j</code> from the temporary storage into slot <code>i</code>. */
protected abstract void restore(int i, int j);
/** Compare element <code>i</code> from the temporary storage with element
* <code>j</code> from the slice to sort, similarly to
* {@link #compare(int, int)}. */
protected abstract int compareSaved(int i, int j);
}