radixsort = {
const backingArray8 = new Uint8Array(256 * 4 * (4 + 2 + 1));
const countingArrays8 = [0, 1, 2, 3].map(i =>
backingArray8.subarray(i * 256, (i + 1) * 256)
);
const backingArray16 = new Uint16Array(backingArray8.buffer);
const countingArrays16 = [0, 1, 2, 3].map(i =>
backingArray16.subarray(512 + i * 256, 512 + (i + 1) * 256)
);
const backingArray32 = new Uint32Array(backingArray8.buffer);
const countingArrays32 = [0, 1, 2, 3].map(i =>
backingArray32.subarray(512 + 256 + i * 256, 512 + 256 + (i + 1) * 256)
);
function radixSortL32(array, left = 0, right = array.length, pass = 0) {
// used to mask out the higher bits for later passes
// pass == 0 for the full 32 bits, pass == 3 for 8 bits
const mask = 0xFFFFFFFF >>> (8 * pass);
let val = (array[left] & mask) >>> 0;
let maxVal = val | 0;
let i = left;
// It's practically free to check if the array is already sorted,
// because we spend most time waiting for reading values of the
// input array and updating the counting arrays
let sorted = true,
prev = val;
if (maxVal < 0x100) {
// 8 bit values
const countingArray = countingArrays32[3];
countingArray.fill(0);
for (; i < right; i++) {
// We really only care about whether or not the max value
// requires 8, 16, 24 or 32 bit, so "maxVal" is a bit
// misleading here, but bitmasking should be faster than testing.
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | val) >>> 0;
// if maxVal is bigger than 8 bits, the current index has to
// fall through to the next appropriate bit width
if (maxVal > 0xFF) break;
// can only reach this point if val < 256
countingArray[val]++;
sorted = sorted && prev <= (prev = val);
}
}
if (maxVal < 0x10000 && i < right) {
// 16 bit values
const countingArray = countingArrays32[2];
countingArray.fill(0);
// if we already advanced a few points, we know that
// these will all go into the first bucket
countingArray[0] = i - left;
for (; i < right; i++) {
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | (val & mask)) >>> 0;
if (maxVal > 0xFFFF) break;
countingArray[(val >>> 8) & 0xFF]++;
sorted = sorted && prev <= (prev = val);
}
}
if (maxVal < 0x1000000 && i < right) {
// 24 bit values
const countingArray = countingArrays32[1];
countingArray.fill(0);
countingArray[0] = i - left;
for (; i < right; i++) {
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | (val & mask)) >>> 0;
if (maxVal > 0xFFFFFF) break;
countingArray[(val >>> 16) & 0xFF]++;
sorted = sorted && prev <= (prev = val);
}
}
if (i < right) {
// remaining 32 bit values
const countingArray = countingArrays32[0];
countingArray.fill(0);
countingArray[0] = i - left;
while (i < right) {
val = array[i++];
countingArray[val >>> 24]++;
sorted = sorted && prev <= (prev = val);
}
}
// Fast path for the sorted (sub)array case
if (sorted) return array;
// Now we do the actual sorting. Again, based on maxVal
// we determine which approach we need
if (maxVal < 0x100) {
// for the last byte we can do a special micro-optimization:
// instead of swapping, overwrite the lowest eight bits
const countingArray = countingArrays32[3];
let i = left,
j = left,
n = 0,
maskVal = array[i] - (array[i] & 0xFF);
while (n < 256 && i < right) {
j += countingArray[n++];
while (i < j) array[i++] = maskVal;
maskVal++;
}
return array;
} else if (maxVal < 0x10000) {
// regardless of what pass we originally had,
// we can adjust to the smallest max value
pass = 2;
} else if (maxVal < 0x1000000) {
pass = 1;
}
const countingArray = countingArrays32[pass];
const shift = 24 - pass * 8;
// Half-swap trick. We lift the first value of every non-empty bucket
// This leaves "holes" in every bucket to be filled. Then we move the
// first lifted value to the correct bucket, advance the idx of that
// bucket to its next value, then move that value, and repeat until
// we reach the end of a bucket. Then we repeat this with the next lifted
// until we run out of lifted values to move.
// This way we can avoid swaps: we can directly move values to the
// correct bucket instead.
const liftedValues = [];
// Re-use 8bit array for storing the starting indices each bucket,
// and convert the regular count array into marking the end-points
// of every bucket (which effectively are the starting indices of
// the next bucket).
const indexArray = countingArrays32[3];
const range = right - left;
const nextSort = range < 256 ? radixSort8 : range < 0xFFFF ? radixSort16 : radixSortL32;
// Create indices array + lift values
if (range === countingArray[0]) {
// If one bucket contains all values in the current range then
// this pass will effectively do nothing. So in that case we
// can immediately recurse to the next pass (or return from the
// range if pass === 3 because that means it is already sorted
return radixSortL32(array, left, right, ++pass);
} else if (countingArray[0] > 0) {
liftedValues.push(array[left]);
}
indexArray[0] = left;
for (let i = 1, prevCount = (countingArray[0] += left); i < 256; i++) {
const count = countingArray[i];
if (range === count) return radixSortL32(array, left, right, ++pass);
else if (count > 0) liftedValues.push(array[prevCount]);
indexArray[i] = prevCount;
prevCount += count;
countingArray[i] = prevCount;
}
// The part where we actually move the values to the correct bucket
for (let i = 0; i < liftedValues.length - 1; i++) {
// We put one of the values in the hole in the
// bucket it belongs, pick the next value from
// that bucket (leaving a hole), put that value
// into the hole in the bucket where it belongs,
// and repeat this process until we reach the end
// of a bucket. At that point we move onto the next
// value in the "hole" aray,
let val = liftedValues[i];
let bucket = (val >>> shift) & 0xFF;
let idx = indexArray[bucket]++;
let end = countingArray[bucket];
array[idx++] = val;
while (idx < end) {
val = array[idx];
bucket = (val >>> shift) & 0xFF;
idx = indexArray[bucket]++;
end = countingArray[bucket];
array[idx++] = val;
}
}
// Clean-up: plug the last value back into the last hole
// Here's a fun mini-optimization: if out of a total of N
// buckets N-1 are "sorted", then the last bucket can only
// contain values that should be there and therefore must
// already be sorted as well.
val = liftedValues[liftedValues.length - 1];
array[indexArray[(val >>> shift) & 0xFF]] = val;
// Move on to the next pass
pass++;
for (let i = 0, sum = left; i < 256; i++) {
const nextSum = countingArray[i];
if (sum < nextSum) {
// insertion sort fallback for small ranges
// TODO: maybe fall-back to quicksort at slightly
// bigger range is also worth it?
const range = nextSum - sum;
if (range <= 40) {
for (let i = sum + 1, j; i < nextSum; i++) {
const v = array[i];
for (j = i - 1; j >= sum && array[j] > v; j--) {
array[j + 1] = array[j];
}
if (++j < i) array[j] = v;
}
}
// else if (range < 256) radixSort8(array, sum, nextSum, pass);
// else if (range < 0xFFFF) radixSort16(array, sum, nextSum, pass);
else radixSortL32(array, sum, nextSum, pass);
}
sum = nextSum;
}
return array;
}
// Range length < 65536
function radixSort16(array, left = 0, right = array.length, pass = 0) {
const mask = 0xFFFFFFFF >>> (8 * pass);
let val = (array[left] & mask) >>> 0;
let maxVal = val | 0;
let i = left;
let sorted = true, prev = val;
if (maxVal < 0x100) {
const countingArray = countingArrays16[3];
countingArray.fill(0);
for (; i < right; i++) {
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | val) >>> 0;
if (maxVal > 0xFF) break;
countingArray[val]++;
sorted = sorted && prev <= (prev = val);
}
}
if (maxVal < 0x10000 && i < right) {
const countingArray = countingArrays16[2];
countingArray.fill(0);
countingArray[0] = i - left;
for (; i < right; i++) {
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | (val & mask)) >>> 0;
if (maxVal > 0xFFFF) break;
countingArray[(val >>> 8) & 0xFF]++;
sorted = sorted && prev <= (prev = val);
}
}
if (maxVal < 0x1000000 && i < right) {
const countingArray = countingArrays16[1];
countingArray.fill(0);
countingArray[0] = i - left;
for (; i < right; i++) {
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | (val & mask)) >>> 0;
if (maxVal > 0xFFFFFF) break;
countingArray[(val >>> 16) & 0xFF]++;
sorted = sorted && prev <= (prev = val);
}
}
if (i < right) {
const countingArray = countingArrays16[0];
countingArray.fill(0);
countingArray[0] = i - left;
while (i < right) {
val = array[i++];
countingArray[val >>> 24]++;
sorted = sorted && prev <= (prev = val);
}
}
if (sorted) return array;
if (maxVal < 0x100) {
const countingArray = countingArrays16[3];
let i = left,
j = left,
n = 0,
maskVal = array[i] - (array[i] & 0xFF);
while (n < 256 && i < right) {
j += countingArray[n++];
while (i < j) array[i++] = maskVal;
maskVal++;
}
return array;
} else if (maxVal < 0x10000) pass = 2;
else if (maxVal < 0x1000000) pass = 1;
const countingArray = countingArrays16[pass];
const shift = 24 - pass * 8;
const liftedValues = [];
const indexArray = countingArrays16[3];
const range = right - left;
const nextSort = range < 256 ? radixSort8 : radixSort16;
if (range === countingArray[0]) {
return radixSort16(array, left, right, ++pass);
} else if (countingArray[0] > 0) {
liftedValues.push(array[left]);
}
indexArray[0] = left;
for (let i = 1, prevCount = (countingArray[0] += left); i < 256; i++) {
const count = countingArray[i];
if (range === count) return radixSort16(array, left, right, ++pass);
else if (count > 0) liftedValues.push(array[prevCount]);
indexArray[i] = prevCount;
prevCount += count;
countingArray[i] = prevCount;
}
for (let i = 0; i < liftedValues.length - 1; i++) {
let val = liftedValues[i];
let bucket = (val >>> shift) & 0xFF;
let idx = indexArray[bucket]++;
let end = countingArray[bucket];
array[idx++] = val;
while (idx < end) {
val = array[idx];
bucket = (val >>> shift) & 0xFF;
idx = indexArray[bucket]++;
end = countingArray[bucket];
array[idx++] = val;
}
}
val = liftedValues[liftedValues.length - 1];
array[indexArray[(val >>> shift) & 0xFF]] = val;
pass++;
for (let i = 0, sum = left; i < 256; i++) {
const nextSum = countingArray[i];
if (sum < nextSum) {
const range = nextSum - sum;
if (range <= 40) {
for (let i = sum + 1, j; i < nextSum; i++) {
const v = array[i];
for (j = i - 1; j >= sum && array[j] > v; j--) {
array[j + 1] = array[j];
}
if (++j < i) array[j] = v;
}
}
// else if (range < 256) radixSort16(array, sum, nextSum, pass);
else radixSort16(array, sum, nextSum, pass);
}
sum = nextSum;
}
return array;
}
// Array length less than 256
function radixSort8(array, left = 0, right = array.length, pass = 0) {
const mask = 0xFFFFFFFF >>> (8 * pass);
let val = (array[left] & mask) >>> 0;
let maxVal = val | 0;
let i = left;
let sorted = true, prev = val;
if (maxVal < 0x100) {
const countingArray = countingArrays8[3];
countingArray.fill(0);
for (; i < right; i++) {
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | val) >>> 0;
if (maxVal > 0xFF) break;
countingArray[val]++;
sorted = sorted && prev <= (prev = val);
}
}
if (maxVal < 0x10000 && i < right) {
const countingArray = countingArrays8[2];
countingArray.fill(0);
countingArray[0] = i - left;
for (; i < right; i++) {
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | (val & mask)) >>> 0;
if (maxVal > 0xFFFF) break;
countingArray[(val >>> 8) & 0xFF]++;
sorted = sorted && prev <= (prev = val);
}
}
if (maxVal < 0x1000000 && i < right) {
const countingArray = countingArrays8[1];
countingArray.fill(0);
countingArray[0] = i - left;
for (; i < right; i++) {
val = (array[i] & mask) >>> 0;
maxVal = (maxVal | (val & mask)) >>> 0;
if (maxVal > 0xFFFFFF) break;
countingArray[(val >>> 16) & 0xFF]++;
sorted = sorted && prev <= (prev = val);
}
}
if (i < right) {
const countingArray = countingArrays8[0];
countingArray.fill(0);
countingArray[0] = i - left;
while (i < right) {
val = array[i++];
countingArray[val >>> 24]++;
sorted = sorted && prev <= (prev = val);
}
}
if (sorted) return array;
if (maxVal < 0x100) {
const countingArray = countingArrays8[3];
let i = left,
j = left,
n = 0,
maskVal = array[i] - (array[i] & 0xFF);
while (n < 256 && i < right) {
j += countingArray[n++];
while (i < j) array[i++] = maskVal;
maskVal++;
}
return array;
} else if (maxVal < 0x10000) pass = 2;
else if (maxVal < 0x1000000) pass = 1;
const countingArray = countingArrays8[pass];
const shift = 24 - pass * 8;
const liftedValues = [];
const indexArray = countingArrays8[3];
const range = right - left;
if (range === countingArray[0]) {
return radixSort8(array, left, right, ++pass);
} else if (countingArray[0] > 0) {
liftedValues.push(array[left]);
}
indexArray[0] = left;
for (let i = 1, prevCount = (countingArray[0] += left); i < 256; i++) {
const count = countingArray[i];
if (range === count) return radixSort8(array, left, right, ++pass);
else if (count > 0) liftedValues.push(array[prevCount]);
indexArray[i] = prevCount;
prevCount += count;
countingArray[i] = prevCount;
}
for (let i = 0; i < liftedValues.length - 1; i++) {
let val = liftedValues[i];
let bucket = (val >>> shift) & 0xFF;
let idx = indexArray[bucket]++;
let end = countingArray[bucket];
array[idx++] = val;
while (idx < end) {
val = array[idx];
bucket = (val >>> shift) & 0xFF;
idx = indexArray[bucket]++;
end = countingArray[bucket];
array[idx++] = val;
}
}
val = liftedValues[liftedValues.length - 1];
array[indexArray[(val >>> shift) & 0xFF]] = val;
pass++;
for (let i = 0, sum = left; i < 256; i++) {
const nextSum = countingArray[i];
if (sum < nextSum) {
const range = nextSum - sum;
if (range <= 40) {
for (let i = sum + 1, j; i < nextSum; i++) {
const v = array[i];
for (j = i - 1; j >= sum && array[j] > v; j--) {
array[j + 1] = array[j];
}
if (++j < i) array[j] = v;
}
} else radixSort8(array, sum, nextSum, pass);
}
sum = nextSum;
}
return array;
}
return function radixsort(array) {
// // All benchmarks so far suggest that for arrays
// // smaller than this the native sorts definitely win
// if (array.length <= 200) {
// if (Array.isArray(array)) array.sort((a, b) => a - b);
// else array.sort(); // native TypedArray sort
// return array;
// }
const sort = array.length < 256 ? radixSort8 : array.length < 0xFFFF ? radixSort16 : radixSortL32;
return sort(array, 0, array.length, 0);
};
}