sku-pg
Version:
Skulpt is a Javascript implementation of Python 2.x. Python that runs in your browser!
736 lines (668 loc) • 23.5 kB
JavaScript
/**
* @constructor
* @param {Sk.builtin.list=} list
* @param {number=} length optional
* @extends Sk.builtin.object
*/
Sk.builtin.timSort = function (list, length) {
this.list = new Sk.builtin.list(list.v);
// When we get into galloping mode, we stay there until both runs win less
// often than MIN_GALLOP consecutive times. See listsort.txt for more info.
this.MIN_GALLOP = 7;
if (length) {
this.listlength = length;
} else {
this.listlength = list.sq$length();
}
};
Sk.builtin.timSort.prototype.lt = function (a, b) {
return Sk.misceval.richCompareBool(a, b, "Lt");
};
Sk.builtin.timSort.prototype.le = function (a, b) {
return !this.lt(b, a);
};
Sk.builtin.timSort.prototype.setitem = function (item, value) {
this.list.v[item] = value;
};
/*
# binarysort is the best method for sorting small arrays: it does
# few compares, but can do data movement quadratic in the number of
# elements.
# "a" is a contiguous slice of a list, and is sorted via binary insertion.
# This sort is stable.
# On entry, the first "sorted" elements are already sorted.
# Even in case of error, the output slice will be some permutation of
# the input (nothing is lost or duplicated)
*/
Sk.builtin.timSort.prototype.binary_sort = function (a, sorted) {
var pivot;
var p;
var r;
var l;
var start;
for (start = a.base + sorted; start < a.base + a.len; start++) {
l = a.base;
r = start;
pivot = a.getitem(r);
// Invariants:
// pivot >= all in [base, l).
// pivot < all in [r, start).
// The second is vacuously true at the start.
while (l < r) {
p = l + ((r - l) >> 1);
if (this.lt(pivot, a.getitem(p))) {
r = p;
} else {
l = p + 1;
}
}
goog.asserts.assert(l === r);
// The invariants still hold, so pivot >= all in [base, l) and
// pivot < all in [l, start), so pivot belongs at l. Note
// that if there are elements equal to pivot, l points to the
// first slot after them -- that's why this sort is stable.
// Slide over to make room.
for (p = start; p > l; p--) {
a.setitem(p, a.getitem(p - 1));
}
a.setitem(l, pivot);
}
};
Sk.builtin.timSort.prototype.count_run = function (a) {
/*
# Compute the length of the run in the slice "a".
# "A run" is the longest ascending sequence, with
#
# a[0] <= a[1] <= a[2] <= ...
#
# or the longest descending sequence, with
#
# a[0] > a[1] > a[2] > ...
#
# Return (run, descending) where descending is False in the former case,
# or True in the latter.
# For its intended use in a stable mergesort, the strictness of the defn of
# "descending" is needed so that the caller can safely reverse a descending
# sequence without violating stability (strict > ensures there are no equal
# elements to get out of order).
*/
var n;
var p;
var descending;
if (a.len <= 1) {
n = a.len;
descending = false;
} else {
n = 2;
if (this.lt(a.getitem(a.base + 1), a.getitem(a.base))) {
descending = true;
for (p = a.base + 2; p < a.base + a.len; p++) {
if (this.lt(a.getitem(p), a.getitem(p - 1))) {
n++;
} else {
break;
}
}
} else {
descending = false;
for (p = a.base + 2; p < a.base + a.len; p++) {
if (this.lt(a.getitem(p), a.getitem(p - 1))) {
break;
} else {
n++;
}
}
}
}
return {"run": new Sk.builtin.listSlice(a.list, a.base, n), "descending": descending};
};
Sk.builtin.timSort.prototype.sort = function () {
/*
# ____________________________________________________________
# Entry point.
*/
var minrun;
var cr;
var sorted;
var remaining = new Sk.builtin.listSlice(this.list, 0, this.listlength);
if (remaining.len < 2) {
return;
}
// March over the array once, left to right, finding natural runs,
// and extending short natural runs to minrun elements.
this.merge_init();
minrun = this.merge_compute_minrun(remaining.len);
while (remaining.len > 0) {
// Identify next run.
cr = this.count_run(remaining);
if (cr.descending) {
cr.run.reverse();
}
// If short, extend to min(minrun, nremaining).
if (cr.run.len < minrun) {
sorted = cr.run.len;
if (minrun < remaining.len) {
cr.run.len = minrun;
} else {
cr.run.len = remaining.len;
}
this.binary_sort(cr.run, sorted);
}
// Advance remaining past this run.
remaining.advance(cr.run.len);
// Push run onto pending-runs stack, and maybe merge.
this.pending.push(cr.run);
this.merge_collapse();
}
goog.asserts.assert(remaining.base == this.listlength);
this.merge_force_collapse();
goog.asserts.assert(this.pending.length == 1);
goog.asserts.assert(this.pending[0].base === 0);
goog.asserts.assert(this.pending[0].len == this.listlength);
};
/*
# Locate the proper position of key in a sorted vector; if the vector
# contains an element equal to key, return the position immediately to the
# left of the leftmost equal element -- or to the right of the rightmost
# equal element if the flag "rightmost" is set.
#
# "hint" is an index at which to begin the search, 0 <= hint < a.len.
# The closer hint is to the final result, the faster this runs.
#
# The return value is the index 0 <= k <= a.len such that
#
# a[k-1] < key <= a[k] (if rightmost is False)
# a[k-1] <= key < a[k] (if rightmost is True)
#
# as long as the indices are in bound. 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.
*/
Sk.builtin.timSort.prototype.gallop = function (key, a, hint, rightmost) {
var lower;
var self;
var p;
var lastofs;
var ofs;
var maxofs;
var hintminofs;
var hintminlastofs;
var m;
goog.asserts.assert(0 <= hint && hint < a.len);
self = this;
if (rightmost) {
lower = function (a, b) {
return self.le(a, b);
}; // search for the largest k for which a[k] <= key
} else {
lower = function (a, b) {
return self.lt(a, b);
}; // search for the largest k for which a[k] < key
}
p = a.base + hint;
lastofs = 0;
ofs = 1;
if (lower(a.getitem(p), key)) {
// a[hint] < key -- gallop right, until
// a[hint + lastofs] < key <= a[hint + ofs]
maxofs = a.len - hint; // a[a.len-1] is highest
while (ofs < maxofs) {
if (lower(a.getitem(p + ofs), key)) {
lastofs = ofs;
try {
ofs = (ofs << 1) + 1;
} catch (err) {
ofs = maxofs;
}
} else {
// key <= a[hint + ofs]
break;
}
}
if (ofs > maxofs) {
ofs = maxofs;
}
// Translate back to offsets relative to a.
lastofs += hint;
ofs += hint;
} else {
// key <= a[hint] -- gallop left, until
// a[hint - ofs] < key <= a[hint - lastofs]
maxofs = hint + 1; // a[0] is lowest
while (ofs < maxofs) {
if (lower(a.getitem(p - ofs), key)) {
break;
} else {
// key <= a[hint - ofs]
lastofs = ofs;
try {
ofs = (ofs << 1) + 1;
} catch (err) {
ofs = maxofs;
}
}
}
if (ofs > maxofs) {
ofs = maxofs;
}
// Translate back to positive offsets relative to a.
hintminofs = hint - ofs;
hintminlastofs = hint - lastofs;
lastofs = hintminofs;
ofs = hintminlastofs;
}
goog.asserts.assert(-1 <= lastofs < ofs <= a.len);
// Now a[lastofs] < key <= a[ofs], so key belongs somewhere to the
// right of lastofs but no farther right than ofs. Do a binary
// search, with invariant a[lastofs-1] < key <= a[ofs].
lastofs += 1;
while (lastofs < ofs) {
m = lastofs + ((ofs - lastofs) >> 1);
if (lower(a.getitem(a.base + m), key)) {
lastofs = m + 1; // a[m] < key
} else {
ofs = m; // key <= a[m]
}
}
goog.asserts.assert(lastofs == ofs); // so a[ofs-1] < key <= a[ofs]
return ofs;
};
// ____________________________________________________________
Sk.builtin.timSort.prototype.merge_init = function () {
// This controls when we get *into* galloping mode. It's initialized
// to MIN_GALLOP. merge_lo and merge_hi tend to nudge it higher for
// random data, and lower for highly structured data.
this.min_gallop = this.MIN_GALLOP;
// A stack of n pending runs yet to be merged. Run #i starts at
// address pending[i].base and extends for pending[i].len elements.
// It's always true (so long as the indices are in bounds) that
//
// pending[i].base + pending[i].len == pending[i+1].base
//
// so we could cut the storage for this, but it's a minor amount,
// and keeping all the info explicit simplifies the code.
this.pending = [];
};
// Merge the slice "a" with the slice "b" in a stable way, in-place.
// a.len <= b.len. See listsort.txt for more info.
// a.len and b.len must be > 0, and a.base + a.len == b.base.
// Must also have that b.list[b.base] < a.list[a.base], that
// a.list[a.base+a.len-1] belongs at the end of the merge, and should have
Sk.builtin.timSort.prototype.merge_lo = function (a, b) {
var min_gallop;
var dest;
var acount, bcount;
var p;
goog.asserts.assert(a.len > 0 && b.len > 0 && a.base + a.len == b.base);
min_gallop = this.min_gallop;
dest = a.base;
a = a.copyitems();
// Invariant: elements in "a" are waiting to be reinserted into the list
// at "dest". They should be merged with the elements of "b".
// b.base == dest + a.len.
// We use a finally block to ensure that the elements remaining in
// the copy "a" are reinserted back into this.list in all cases.
try {
this.setitem(dest, b.popleft());
dest++;
if (a.len == 1 || b.len === 0) {
return;
}
while (true) {
acount = 0; // number of times A won in a row
bcount = 0; // number of times B won in a row
// Do the straightforward thing until (if ever) one run
// appears to win consistently.
while (true) {
if (this.lt(b.getitem(b.base), a.getitem(a.base))) {
this.setitem(dest, b.popleft());
dest++;
if (b.len === 0) {
return;
}
bcount++;
acount = 0;
if (bcount >= min_gallop) {
break;
}
} else {
this.setitem(dest, a.popleft());
dest++;
if (a.len == 1) {
return;
}
acount++;
bcount = 0;
if (acount >= min_gallop) {
break;
}
}
}
// 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.
min_gallop += 1;
while (true) {
min_gallop -= min_gallop > 1;
this.min_gallop = min_gallop;
acount = this.gallop(b.getitem(b.base), a, 0, true);
for (p = a.base; p < a.base + acount; p++) {
this.setitem(dest, a.getitem(p));
dest++;
}
a.advance(acount);
if (a.len <= 1) {
return;
}
this.setitem(dest, b.popleft());
dest++;
// a.len==0 is impossible now if the comparison
// function is consistent, but we can't assume
// that it is.
if (b.len === 0) {
return;
}
bcount = this.gallop(a.getitem(a.base), b, 0, false);
for (p = b.base; p < b.base + bcount; p++) {
this.setitem(dest, b.getitem(p));
dest++;
}
b.advance(bcount);
if (b.len === 0) {
return;
}
this.setitem(dest, a.popleft());
dest++;
if (a.len == 1) {
return;
}
if (acount < this.MIN_GALLOP && bcount < this.MIN_GALLOP) {
break;
}
min_gallop++; // penalize it for leaving galloping mode
this.min_gallop = min_gallop;
}
}
}
finally {
// The last element of a belongs at the end of the merge, so we copy
// the remaining elements of b before the remaining elements of a.
goog.asserts.assert(a.len >= 0 && b.len >= 0);
for (p = b.base; p < b.base + b.len; p++) {
this.setitem(dest, b.getitem(p));
dest++;
}
for (p = a.base; p < a.base + a.len; p++) {
this.setitem(dest, a.getitem(p));
dest++;
}
}
};
Sk.builtin.timSort.prototype.merge_hi = function (a, b) {
var min_gallop;
var dest;
var acount, bcount, nexta, nextb;
var k;
var p;
goog.asserts.assert(a.len > 0 && b.len > 0 && a.base + a.len == b.base);
min_gallop = this.min_gallop;
dest = b.base + b.len;
b = b.copyitems();
// Invariant: elements in "a" are waiting to be reinserted into the list
// at "dest". They should be merged with the elements of "b".
// b.base == dest + a.len.
// We use a finally block to ensure that the elements remaining in
// the copy "a" are reinserted back into this.list in all cases.
try {
dest--;
this.setitem(dest, a.popright());
if (a.len === 0 || b.len == 1) {
return;
}
while (true) {
acount = 0; // number of times A won in a row
bcount = 0; // number of times B won in a row
// Do the straightforward thing until (if ever) one run
// appears to win consistently.
while (true) {
nexta = a.getitem(a.base + a.len - 1);
nextb = b.getitem(b.base + b.len - 1);
if (this.lt(nextb, nexta)) {
dest--;
this.setitem(dest, nexta);
a.len--;
if (a.len === 0) {
return;
}
acount++;
bcount = 0;
if (acount >= min_gallop) {
break;
}
} else {
dest--;
this.setitem(dest, nextb);
b.len--;
if (b.len == 1) {
return;
}
bcount++;
acount = 0;
if (bcount >= min_gallop) {
break;
}
}
}
// 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.
min_gallop += 1;
while (true) {
min_gallop -= min_gallop > 1;
this.min_gallop = min_gallop;
nextb = b.getitem(b.base + b.len - 1);
k = this.gallop(nextb, a, a.len - 1, true);
acount = a.len - k;
for (p = a.base + a.len - 1; p > a.base + k - 1; p--) {
dest--;
this.setitem(dest, a.getitem(p));
}
a.len -= acount;
if (a.len === 0) {
return;
}
dest--;
this.setitem(dest, b.popright());
if (b.len == 1) {
return;
}
nexta = a.getitem(a.base + a.len - 1);
k = this.gallop(nexta, b, b.len - 1, false);
bcount = b.len - k;
for (p = b.base + b.len - 1; p > b.base + k - 1; p--) {
dest--;
this.setitem(dest, b.getitem(p));
}
b.len -= bcount;
// b.len==0 is impossible now if the comparison
// function is consistent, but we can't assume
// that it is.
if (b.len <= 1) {
return;
}
dest--;
this.setitem(dest, a.popright());
if (a.len === 0) {
return;
}
if (acount < this.MIN_GALLOP && bcount < this.MIN_GALLOP) {
break;
}
min_gallop++; // penalize it for leaving galloping mode
this.min_gallop = min_gallop;
}
}
}
finally {
// The last element of a belongs at the end of the merge, so we copy
// the remaining elements of b before the remaining elements of a.
goog.asserts.assert(a.len >= 0 && b.len >= 0);
for (p = a.base + a.len - 1; p > a.base - 1; p--) {
dest--;
this.setitem(dest, a.getitem(p));
}
for (p = b.base + b.len - 1; p > b.base - 1; p--) {
dest--;
this.setitem(dest, b.getitem(p));
}
}
};
// Merge the two runs at stack indices i and i+1.
Sk.builtin.timSort.prototype.merge_at = function (i) {
var a;
var b;
var k;
if (i < 0) {
i = this.pending.length + i;
}
a = this.pending[i];
b = this.pending[i + 1];
goog.asserts.assert(a.len > 0 && b.len > 0);
goog.asserts.assert(a.base + a.len == b.base);
// Record the length of the combined runs and remove the run b
this.pending[i] = new Sk.builtin.listSlice(this.list, a.base, a.len + b.len);
this.pending.splice(i + 1, 1);
// Where does b start in a? Elements in a before that can be
// ignored (already in place).
k = this.gallop(b.getitem(b.base), a, 0, true);
a.advance(k);
if (a.len === 0) {
return;
}
// Where does a end in b? Elements in b after that can be
// ignored (already in place).
b.len = this.gallop(a.getitem(a.base + a.len - 1), b, b.len - 1, false);
if (b.len === 0) {
return;
}
// Merge what remains of the runs. The direction is chosen to
// minimize the temporary storage needed.
if (a.len <= b.len) {
this.merge_lo(a, b);
} else {
this.merge_hi(a, b);
}
};
// 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 listsort.txt for more info.
Sk.builtin.timSort.prototype.merge_collapse = function () {
var p = this.pending;
while (p.length > 1) {
if (p.length >= 3 && p[p.length - 3].len <= p[p.length - 2].len + p[p.length - 1].len) {
if (p[p.length - 3].len < p[p.length - 1].len) {
this.merge_at(-3);
} else {
this.merge_at(-2);
}
} else if (p[p.length - 2].len <= p[p.length - 1].len) {
this.merge_at(-2);
} else {
break;
}
}
};
// Regardless of invariants, merge all runs on the stack until only one
// remains. This is used at the end of the mergesort.
Sk.builtin.timSort.prototype.merge_force_collapse = function () {
var p = this.pending;
while (p.length > 1) {
if (p.length >= 3 && p[p.length - 3].len < p[p.length - 1].len) {
this.merge_at(-3);
} else {
this.merge_at(-2);
}
}
};
// Compute a good value for the minimum run length; natural runs shorter
// than this are boosted artificially via binary insertion.
//
// If n < 64, return n (it's too small to bother with fancy stuff).
// Else if n is an exact power of 2, return 32.
// Else return an int k, 32 <= k <= 64, such that n/k is close to, but
// strictly less than, an exact power of 2.
//
// See listsort.txt for more info.
Sk.builtin.timSort.prototype.merge_compute_minrun = function (n) {
var r = 0; // becomes 1 if any 1 bits are shifted off
while (n >= 64) {
r = r | n & 1;
n >>= 1;
}
return n + r;
};
//ListSlice
/**
* @constructor
* @param {Sk.builtin.list=} list
* @param {number=} base
* @param {number=} len
* @extends Sk.builtin.object
*/
Sk.builtin.listSlice = function (list, base, len) {
this.list = list;
this.base = base;
this.len = len;
};
Sk.builtin.listSlice.prototype.copyitems = function () {
//Make a copy of the slice of the original list
var start = this.base;
var stop = this.base + this.len;
goog.asserts.assert(0 <= start <= stop);
return new Sk.builtin.listSlice(new Sk.builtin.list(this.list.v.slice(start, stop)), 0, this.len);
};
Sk.builtin.listSlice.prototype.advance = function (n) {
this.base += n;
this.len -= n;
goog.asserts.assert(this.base <= this.list.sq$length());
};
Sk.builtin.listSlice.prototype.getitem = function (item) {
return this.list.v[item];
};
Sk.builtin.listSlice.prototype.setitem = function (item, value) {
this.list.v[item] = value;
};
Sk.builtin.listSlice.prototype.popleft = function () {
var result = this.list.v[this.base];
this.base++;
this.len--;
return result;
};
Sk.builtin.listSlice.prototype.popright = function () {
this.len--;
return this.list.v[this.base + this.len];
};
Sk.builtin.listSlice.prototype.reverse = function () {
// Reverse the slice in-place.
var list_hi;
var list_lo;
var list = this.list;
var lo = this.base;
var hi = lo + this.len - 1;
while (lo < hi) {
list_hi = list.v[hi];
list_lo = list.v[lo];
list.v[lo] = list_hi;
list.v[hi] = list_lo;
lo++;
hi--;
}
};
goog.exportSymbol("Sk.builtin.listSlice", Sk.builtin.listSlice);
goog.exportSymbol("Sk.builtin.timSort", Sk.builtin.timSort);