array-sorting-algorithms
Version:
A package which implements many different sorting algorithms for sorting arrays
1,565 lines (1,273 loc) • 60.1 kB
JavaScript
class SortingAlgorithms {
/*
Comparison sorts
*/
/** Returns a sorted array using bobble sort
* {Stable: Yes, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static bubbleSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`bubbleSort() expects an array! Found ${typeof arr}.`);
}
let swapping = true;
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
while (swapping) {
swapping = false;
for (let i = 0; i < arr.length - 1; i++) {
if (order.toLowerCase() === 'asc') {
if (arr[i] < arr[i + 1]) {
SortingAlgorithms.swap(arr, i, i + 1);
swapping = true;
}
} else {
if (arr[i] > arr[i + 1]) {
SortingAlgorithms.swap(arr, i, i + 1);
swapping = true;
}
}
}
}
return arr;
}
/** Returns a sorted array using bingo sort
* {Stable: No, Time: O(n*m), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static bingoSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`bingoSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
for (let i = 0; i < arr.length - 1; i++) {
let min = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
if (min !== i) {
SortingAlgorithms.swap(arr, min, i)
}
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using comb sort
* {Stable: No, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static combSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`combSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
const nextGap = gap => {
gap = (gap * 10) / 13;
if (gap < 1) {
return 1;
}
return gap;
}
let
gap = arr.length,
swapping = true;
while (gap !== 1 || swapping) {
gap = nextGap(gap);
swapping = false;
for (let i = 0; i < arr.length - gap; i++) {
if (order === 'asc' && arr[i] < arr[i + gap]) {
SortingAlgorithms.swap(arr, i, i + gap);
swapping = true;
} else if (order === 'des' && arr[i] > arr[i + gap]) {
SortingAlgorithms.swap(arr, i, i + gap);
swapping = true;
}
}
}
return arr;
}
/** Returns a sorted array using cycle sort
* {Stable: No, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
*/
static cycleSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`cycleSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
for (let ci = 0; ci < arr.length - 1; ci++) {
let
item = arr[ci],
copyIndex = ci;
for (let i = ci + 1; i < arr.length; i++) {
if (arr[i] < item) {
copyIndex++;
}
}
if (ci === copyIndex) {
continue;
}
while (item === arr[copyIndex]) {
copyIndex++
}
[arr[copyIndex], item] = [item, arr[copyIndex]]
while (copyIndex !== ci) {
copyIndex = ci
for (let i = ci + 1; i < arr.length; i++) {
if (arr[i] < item) {
copyIndex++;
}
}
// skip duplicates
while (item === arr[copyIndex]) {
copyIndex++
}
[arr[copyIndex], item] = [item, arr[copyIndex]]
}
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using cocktail sort
* {Stable: Yes, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
*/
static cocktailSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`cycleSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
let swapping = true;
while (swapping) {
swapping = false;
for (let i = 0; i < arr.length - 1; i++) {
if (order.toLowerCase() === 'asc') {
if (arr[i] < arr[i + 1]) {
SortingAlgorithms.swap(arr, i, i + 1);
swapping = true;
}
} else {
if (arr[i] > arr[i + 1]) {
SortingAlgorithms.swap(arr, i, i + 1);
swapping = true;
}
}
}
if (!swapping) {
break;
}
swapping = false;
for (let i = arr.length - 1; i > 0; i--) {
if (order.toLowerCase() === 'asc') {
if (arr[i - 1] < arr[i]) {
SortingAlgorithms.swap(arr, i, i - 1);
swapping = true;
}
} else {
if (arr[i - 1] > arr[i]) {
SortingAlgorithms.swap(arr, i, i - 1);
swapping = true;
}
}
}
}
return arr;
}
/** Returns a sorted array using gnome sort
* {Stable: Yes, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
*/
static gnomeSort(arr, order = 'des') {
//alternative
// static gnomeSort(sortMe) {
// i = 0;
// while (i < sortMe.length) {
// if (i == 0 || sortMe[i - 1] <= sortMe[i]) {
// i++;
// } else {
// let tmp = sortMe[i];
// sortMe[i] = sortMe[i - 1];
// sortMe[--i] = tmp;
// }
// }
// }
if (!Array.isArray(arr)) {
throw new Error(`cycleSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
const moveBack = (i) => {
for (; i > 0 && arr[i - 1] > arr[i]; i--) {
SortingAlgorithms.swap(arr, i, i - 1)
}
}
for (let i = 1; i < arr.length; i++) {
if (arr[i - 1] > arr[i]) {
moveBack(i)
}
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using max heap sort
* {Stable: No, Time: O(n*log(n)), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
*/
static maxHeapSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`maxHeapSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
for (let i = Math.floor(arr.length / 2 - 1); i >= 0; i--) {
SortingAlgorithms.maxHeapify(arr, arr.length, i);
}
for (let i = arr.length - 1; i >= 0; i--) {
SortingAlgorithms.swap(arr, i, 0);
SortingAlgorithms.maxHeapify(arr, i, 0);
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using min heap sort
* {Stable: No, Time: O(n*log(n)), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is asc
*/
static minHeapSort(arr, order = 'asc') {
if (!Array.isArray(arr)) {
throw new Error(`minHeapSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
for (let i = Math.floor(arr.length / 2 - 1); i >= 0; i--) {
SortingAlgorithms.minHeapify(arr, arr.length, i);
}
for (let i = arr.length - 1; i >= 0; i--) {
SortingAlgorithms.swap(arr, i, 0);
SortingAlgorithms.minHeapify(arr, i, 0);
}
return order === 'des'
? arr.reverse()
: arr;
}
/** Returns a sorted array using insertion sort
* {Stable: Yes, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static insertionSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`insertionSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
for (let i = 0; i < arr.length; i++) {
let
insertVal = arr[i],
pos = i;
if (order.toLowerCase() === 'asc') {
while (0 < pos && arr[pos - 1] < insertVal) {
arr[pos] = arr[pos - 1];
pos--;
}
} else {
while (0 < pos && arr[pos - 1] > insertVal) {
arr[pos] = arr[pos - 1];
pos--;
}
}
arr[pos] = insertVal;
}
return arr;
}
/** Returns a sorted array using binary insertion sort
* {Stable: Yes, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static binaryInsertionSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`countingSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
let
insertPose,
temp;
for (let i = 1; i < arr.length; i++) {
insertPose = SortingAlgorithms.binarySearch(arr, arr[i], 0, i);
if (insertPose < i) {
temp = arr[i];
for (let j = i - 1; j >= insertPose; j--) {
arr[j + 1] = arr[j];
}
arr[insertPose] = temp;
}
}
return order === 'des'
? arr.reverse()
: arr;
}
/** Returns a sorted array using introspective sort
* {Stable: No, Time: O(n*log(n)), Memory: O(log(n))}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
*/
static introSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`introSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
const pivotIndex = SortingAlgorithms.partition(arr, 0, arr.length - 1);
if (pivotIndex < 16) {
arr = SortingAlgorithms.insertionSort(arr);
} else if (pivotIndex > (2 * Math.log(arr.length))) {
arr = SortingAlgorithms.maxHeapSort(arr);
} else {
arr = SortingAlgorithms.quickSort(arr, 0, arr.length - 1);
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using merge sort
* {Stable: Yes, Time: O(n*log(n)), Memory: O(n)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static mergeSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`mergeSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
if (arr.length === 1) {
return arr;
}
const merge = (leftArr, rightArr) => {
const sortedArr = [];
while (leftArr.length && rightArr.length) {
if (order === 'asc') {
leftArr[0] > rightArr[0]
? sortedArr.push(leftArr.shift())
: sortedArr.push(rightArr.shift());
} else {
leftArr[0] < rightArr[0]
? sortedArr.push(leftArr.shift())
: sortedArr.push(rightArr.shift());
}
}
return sortedArr.concat(leftArr).concat(rightArr);
}
const
middle = Math.floor((arr.length / 2)),
leftArr = arr.splice(0, middle),
rightArr = arr;
return merge(this.mergeSort(leftArr, order), this.mergeSort(rightArr, order));
}
/** Returns a sorted array using odd even sort
* {Stable: Yes, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static oddEvenSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`countingSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
let swapping = true;
while (swapping) {
swapping = false;
for (let i = 1; i < arr.length - 1; i += 2) {
if (order.toLowerCase() === 'asc') {
if (arr[i] < arr[i + 1]) {
SortingAlgorithms.swap(arr, i, i + 1);
swapping = true;
}
} else {
if (arr[i] > arr[i + 1]) {
SortingAlgorithms.swap(arr, i, i + 1);
swapping = true;
}
}
}
for (let i = 0; i < arr.length - 1; i += 2) {
if (order.toLowerCase() === 'asc') {
if (arr[i] < arr[i + 1]) {
SortingAlgorithms.swap(arr, i, i + 1);
swapping = true;
}
} else {
if (arr[i] > arr[i + 1]) {
SortingAlgorithms.swap(arr, i, i + 1);
swapping = true;
}
}
}
}
return arr;
}
/** Returns a sorted array using patience sort
* {Stable: No, Time: O(n*log(n)), Memory: O(n)}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
*/
static patienceSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`cycleSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
const piles = []
for (let i = 0; i < arr.length; i++) {
const num = arr[i]
const destinationPileIndex = piles.findIndex(
(pile) => num >= pile[pile.length - 1]
)
if (destinationPileIndex === -1) {
piles.push([num])
} else {
piles[destinationPileIndex].push(num)
}
}
for (let i = 0; i < arr.length; i++) {
let destinationPileIndex = 0
for (let p = 1; p < piles.length; p++) {
const pile = piles[p]
if (pile[0] < piles[destinationPileIndex][0]) {
destinationPileIndex = p
}
}
const distPile = piles[destinationPileIndex]
arr[i] = distPile.shift()
if (distPile.length === 0) {
piles.splice(destinationPileIndex, 1)
}
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using quick sort
* {Stable: No, Time: O(n*log(n)), Memory: O(log(n)))}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
* @param {Number} leftBound Index of the left bound
* @param {Number} rightBound Index of the right bound
*/
static quickSort(arr, order = 'des', leftBound = 0, rightBound = arr.length - 1) {
if (!Array.isArray(arr)) {
throw new Error(`quickSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
if (leftBound < rightBound) {
const pivotIndex = SortingAlgorithms.partition(arr, leftBound, rightBound);
this.quickSort(arr, order, leftBound, pivotIndex - 1);
this.quickSort(arr, order, pivotIndex, rightBound);
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using selection sort
* {Stable: No, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static selectionSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`selectionSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
for (let i = 0; i < arr.length - 1; i++) {
let min = i;
for (let j = i + 1; j < arr.length + 1; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
if (min !== i) {
SortingAlgorithms.swap(arr, min, i);
}
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using double selection sort
* {Stable: No, Time: O(n^2), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static doubleSelectionSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`countingSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
for (let i = 0, j = arr.length - 1; i < j; i++, j--) {
let
min = arr[i],
max = arr[i],
minIndex = i,
maxIndex = i;
for (let k = i; k <= j; k++) {
if (arr[k] > max) {
max = arr[k];
maxIndex = k;
} else if (arr[k] < min) {
min = arr[k];
minIndex = k;
}
}
SortingAlgorithms.swap(arr, i, minIndex)
if (arr[minIndex] === max) {
SortingAlgorithms.swap(arr, j, minIndex);
} else {
SortingAlgorithms.swap(arr, j, maxIndex);
}
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using shell sort
* {Stable: No, Time: O(n*log(n)), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
*/
static shellSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`ShellSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
for (let h = arr.length; h > 0; h = parseInt(h / 2)) {
for (let i = h; i < arr.length; i++) {
let k = arr[i],
j;
for (j = i; j >= h && k < arr[j - h]; j -= h) {
arr[j] = arr[j - h];
}
arr[j] = k;
}
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using tim sort
* {Stable: Yes, Time: O(n*log(n)), Memory: O(n)}
* @param {Array} arr Array of numbers or strings
* @param {String} order Sorting order, asc or des. Default is des
*/
static timSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`countingSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
let MIN_GALLOP = 7;
// === [SPLIT IMPROVEMENTS] === //
// timsort: main loop
const mainLoop = (arr, first, last, lessThan) => {
if (last - first <= 1) return arr;
const state = {
arr: arr,
lessThan: lessThan,
lessThanEqual: function (a, b) { return !lessThan(b, a); },
runStack: [],
remain: first,
last: last,
minrun: calcMinrun(last - first),
minGallop: MIN_GALLOP,
};
while (nextRun(state)) {
while (whenMerge(state)) {
mergeTwoRuns(state);
}
}
return arr;
};
// calculate minimum run size
const calcMinrun = n => {
// from python listsort
// e.g. 1=>1, ..., 63=>63, 64=>32, 65=>33, ..., 127=>64, 128=>32, ...
let r = 0;
while (n >= 64) {
r = r | n & 1;
n = n >> 1;
}
return n + r;
};
// cut array to monotonic chunks named (natural) "run"
const nextRun = state => {
if (state.remain >= state.last) return false;
if (state.last - state.remain <= 1) {
cutRun(state, state.last);
return true;
}
let
last = state.remain,
prev = state.arr[last++],
lastVal = state.arr[last++];
if (state.lessThanEqual(prev, lastVal)) {
prev = lastVal;
while (last < state.last) {
let val = state.arr[last];
// inc-run elems allowed prev == val
if (!state.lessThanEqual(prev, val)) break;
prev = val;
last++;
}
} else {
prev = lastVal;
while (last < state.last) {
let val = state.arr[last];
// dec-run elems must prev > val, prev == val not allowed
if (!state.lessThan(val, prev)) break;
prev = val;
last++;
}
reverse(state.arr, state.remain, last);
}
if (last - state.remain < state.minrun) {
// replace binary sorted minrun
let
minrun = state.remain + state.minrun,
sortStart = last;
last = (minrun > state.last) ? state.last : minrun;
binarySort(state.arr, state.remain, last, state.lessThan, sortStart);
}
cutRun(state, last);
return true;
};
// cut and stack a run
const cutRun = (state, last) => {
const run = {
first: state.remain,
last: last,
length: last - state.remain,
};
state.runStack.push(run);
state.remain = last;
};
// reverse elements in array range
const reverse = (arr, first, last) => {
last--;
while (first < last) {
SortingAlgorithms.swap(arr, first++, last--);
}
};
// loop condition when merge neighbors
const whenMerge = state => {
if (state.remain === state.last) {
return state.runStack.length > 1;
}
if (state.runStack.length <= 1) {
return false;
}
// similar sized runs should be merged: it introduces log(n) merge count
let
curRun = state.runStack[state.runStack.length - 1],
preRun = state.runStack[state.runStack.length - 2];
if (state.runStack.length === 2) return preRun.length <= curRun.length;
let pre2Run = state.runStack[state.runStack.length - 3];
return pre2Run.length <= preRun.length + curRun.length;
};
// merge two chunks
const mergeTwoRuns = state => {
if (state.runStack.length > 2 &&
state.runStack[state.runStack.length - 3].length <
state.runStack[state.runStack.length - 1].length) {
// merge last two runs if stack top run is larger than last two runs
// e.g. run length of stack state changed as [..., 5,2,6] => [..., 7,6]
let curRun = state.runStack.pop();
mergeHeadRuns(state);
state.runStack.push(curRun);
} else {
mergeHeadRuns(state);
}
};
// merge neighbor chunks and add two stacked runs to single run
const mergeHeadRuns = state => {
let
mergerRun = state.runStack.pop(),
mergedRun = state.runStack[state.runStack.length - 1];
// assert mergedRun.last === mergerRun.first
mergeNeighbor(
state.arr, mergedRun.first, mergerRun.first, mergerRun.last,
state);
mergedRun.last = mergerRun.last;
mergedRun.length += mergerRun.length;
};
// === [MERGE IMPROVEMENTS] === //
// merge neighbor chunks
const mergeNeighbor = (arr, first, connect, last, state) => {
let
llength = connect - first,
rlength = last - connect;
if (llength < rlength) {
return mergeIntoLeft(arr, first, connect, last, state);
} else {
return mergeIntoRight(arr, first, connect, last, state);
}
};
// merge with filling to smaller side
const mergeIntoLeft = (arr, first, connect, last, state) => {
// packed states of the function
let m = {};
// escape shorter buffer only
m.right = arr;
m.rcur = connect; m.rlast = last;
// find merge start point which is insert point for first of larger side
m.cur = binSearch(arr, first, connect, m.right[m.rcur], state.lessThan);
m.left = arr.slice(m.cur, connect);
m.lcur = 0; m.llast = connect - m.cur;
// states for mode control
m.galloping = false;
m.gallopingOut = false;
m.selectLeft = true;
m.selectCount = 0;
while (m.lcur < m.llast && m.rcur < m.rlast) {
if (!m.galloping) {
mergeLeftOnePairMode(arr, state, m);
} else {
mergeLeftGallopingMode(arr, state, m);
}
}
// copy back to escaped side (the loop may be empty)
while (m.lcur < m.llast) arr[m.cur++] = m.left[m.lcur++];
return arr;
};
// one pair mode when filling to smaller side
const mergeLeftOnePairMode = (arr, state, m) => {
let
lval = m.left[m.lcur],
rval = m.right[m.rcur];
if (state.lessThanEqual(lval, rval)) { // for sort stable
arr[m.cur++] = lval; m.lcur++;
modeControlInOnePairMode(state, m, !m.selectLeft);
} else {
arr[m.cur++] = rval; m.rcur++;
modeControlInOnePairMode(state, m, m.selectLeft);
}
};
// mode control for one pair mode
const modeControlInOnePairMode = (state, m, selectSwitched) => {
if (selectSwitched) {
m.selectLeft = !m.selectLeft;
m.selectCount = 0;
}
m.selectCount++;
if (m.selectCount >= state.minGallop) {
m.galloping = true;
m.selectCount = 0;
}
};
// galloping mode when filling to smaller side
const mergeLeftGallopingMode = (arr, state, m) => {
if (state.minGallop > 0) state.minGallop--;
let
lval = m.left[m.lcur],
rval = m.right[m.rcur];
if (state.lessThanEqual(lval, rval)) {
// left(shorter) side gallop includes right side first (rightmost)
let end = gallopFirstSearch(
m.left, m.lcur + 1, m.llast, rval, state.lessThan);
modeControlInGallopingMode(state, m, end - m.lcur);
while (m.lcur < end) arr[m.cur++] = m.left[m.lcur++];
} else {
// right(longer) side gallop excludes left side first (leftmost)
let end = gallopFirstSearch(
m.right, m.rcur + 1, m.rlast, lval, state.lessThanEqual);
modeControlInGallopingMode(state, m, end - m.rcur);
while (m.rcur < end) arr[m.cur++] = m.right[m.rcur++];
}
};
// mode control for galloping mode
const modeControlInGallopingMode = (state, m, gallopSize) => {
if (gallopSize < MIN_GALLOP) {
if (m.gallopOut) { // exit galloping mode if gallop out at both sides
m.galloping = false;
m.gallopOut = false;
state.minGallop++;
} else {
m.gallopOut = true;
}
} else {
m.gallopOut = false;
}
};
// merge with filling to larger side
const mergeIntoRight = (arr, first, connect, last, state) => {
// packed states of the function
let m = {};
// escape shorter buffer only
m.left = arr
m.lcur = connect; m.lfirst = first;
// find merge start point which is insert point for first of larger side
m.cur = binSearch(
arr, connect, last, m.left[m.lcur - 1], state.lessThanEqual);
m.right = arr.slice(connect, m.cur);
m.rcur = m.cur - connect; m.rfirst = 0;
// states for mode control
m.galloping = false;
m.gallopingOut = false;
m.selectLeft = true;
m.selectCount = 0;
while (m.lfirst < m.lcur && m.rfirst < m.rcur) {
if (!m.galloping) {
mergeRightOnePairMode(arr, state, m);
} else {
mergeRightGallopingMode(arr, state, m);
}
}
// copy back to escaped side (the loop may be empty)
while (m.rfirst < m.rcur) arr[--m.cur] = m.right[--m.rcur];
return arr;
};
// one pair mode when filling to larger side
const mergeRightOnePairMode = (arr, state, m) => {
let
lval = m.left[m.lcur - 1],
rval = m.right[m.rcur - 1];
if (state.lessThan(rval, lval)) { // (lval > rval) for sort stable
arr[--m.cur] = lval; --m.lcur;
modeControlInOnePairMode(state, m, !m.selectLeft);
} else {
arr[--m.cur] = rval; --m.rcur;
modeControlInOnePairMode(state, m, m.selectLeft);
}
};
// galloping mode when filling to larger side
const mergeRightGallopingMode = (arr, state, m) => {
if (state.minGallop > 0) state.minGallop--;
let
lval = m.left[m.lcur - 1],
rval = m.right[m.rcur - 1];
if (state.lessThan(rval, lval)) {
// left(longer) side gallop excludes right side last (rightmost)
let begin = gallopLastSearch(
m.left, m.lfirst, m.lcur - 1, rval, state.lessThan);
modeControlInGallopingMode(state, m, m.lcur - begin);
while (begin < m.lcur) arr[--m.cur] = m.left[--m.lcur];
} else {
// right(shorter) side gallop includes left side last (leftmost)
let begin = gallopLastSearch(
m.right, m.rfirst, m.rcur - 1, lval, state.lessThanEqual);
modeControlInGallopingMode(state, m, m.rcur - begin);
while (begin < m.rcur) arr[--m.cur] = m.right[--m.rcur];
}
};
// binsearch for gallop mode from first element side
// search to one of regions [0,1) [1,3),[3,7),[7,15),...
const gallopFirstSearch = (arr, first, last, value, lessThan) => {
let
pre = 0,
offset = 1;
while (first + offset < last) {
if (lessThan(value, arr[first + offset])) break;
pre = offset;
offset = (offset << 1) + 1;
}
let
searchFirst = first + pre,
searchLast = (first + offset < last) ? first + offset : last;
return binSearch(arr, searchFirst, searchLast, value, lessThan);
};
// binsearch for gallop mode from last element side
// search to one of regions(from last) [-1,-0),[-3,-1),[-7,-3),[-15,-7),...
const gallopLastSearch = (arr, first, last, value, lessThan) => {
let
pre = 0,
offset = 1;
while (first < last - offset) {
if (!lessThan(value, arr[last - offset])) break;
pre = offset;
offset = (offset << 1) + 1;
}
let
searchFirst = (first < last - offset) ? last - offset : first,
searchLast = last - pre;
return binSearch(arr, searchFirst, searchLast, value, lessThan);
};
// binary search
const binSearch = (arr, first, last, value, lessThan) => {
while (first < last) {
let mid = last + ((first - last) >> 1);
if (lessThan(value, arr[mid])) {
last = mid;
} else {
first = mid + 1;
}
}
return first;
};
// binary sort: insertion sort with binary search
const binarySort = (arr, first, last, lessThan, sortStart) => {
sortStart = sortStart || first + 1;
for (let i = sortStart; i < last; i += 1) {
let point = binSearch(arr, first, i, arr[i], lessThan);
cyclicRShift(arr, point, i + 1);
}
return arr;
};
// 1 right cyclic shift of array range
const cyclicRShift = (arr, first, last) => {
if (last - first <= 1) return arr;
let mostRight = arr[last - 1];
// C: memmove(first, first+1, last-first-1)
for (let cur = last - 1; cur > first; cur -= 1) {
arr[cur] = arr[cur - 1];
}
arr[first] = mostRight;
return arr;
};
const builtinLessThan = (a, b) => {
return a < b;
};
// export interface for runner.js
const sort = (arr) => {
const lessThan = arguments[1] || builtinLessThan;
mainLoop(arr, 0, arr.length, lessThan);
return arr;
};
return order.toLowerCase() === 'asc'
? sort(arr).reverse()
: sort(arr);
}
/*
Non-comparison sorts
*/
/** Returns a sorted array using bucket sort
* {(uniform keys) => Stable: Yes, Time: O(n*2^k), Memory: O(n*k), n<<2^k: No}
* {(integer keys) => Stable: Yes, Time: O(n+r), Memory: O(n+r), n<<2^k: Yes}
* @param {Array} arr Array of numbers or strings
* @param {string} order Sorting order, asc or des. Default is des
* @param {Number} bucketSize The amounts of buckets. Default is 5
*/
static bucketSort(arr, order = 'des', bucketSize = 5) {
if (!Array.isArray(arr)) {
throw new Error(`bucketSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
if (arr.length === 0) {
return arr;
}
let
minValue = arr[0],
maxValue = arr[0];
arr.forEach(function (currentVal) {
if (currentVal < minValue) {
minValue = currentVal;
} else if (currentVal > maxValue) {
maxValue = currentVal;
}
})
let
bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1,
allBuckets = new Array(bucketCount);
for (let i = 0; i < allBuckets.length; i++) {
allBuckets[i] = [];
}
arr.forEach(function (currentVal) {
allBuckets[Math.floor((currentVal - minValue) / bucketSize)].push(currentVal);
});
arr.length = 0;
allBuckets.forEach(function (bucket) {
SortingAlgorithms.insertionSort(bucket);
bucket.forEach(function (element) {
arr.push(element)
});
});
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using counting sort
* {Stable: Yes, Time: O(n+r), Memory: O(n+r), n<<2^k: Yes}
* @param {Array} arr Array of numbers
* @param {string} order Sorting order, asc or des. Default is des
*/
static countingSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`countingSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
arr.forEach(number => {
if (isNaN(number)) {
throw new Error(`countingSort() expects an array of numbers! Found ${typeof number}.`);
}
});
arr = arr.reduce((acc, v) => (acc[v] = (acc[v] || 0) + 1, acc), [])
.reduce((acc, n, i) => acc.concat(Array(n).fill(i)), []);
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using flash sort
* {Stable: No, Time: O(n^2), Memory: O(n), n<<2^k: No}
* @param {Array} arr Array of numbers
* @param {string} order Sorting order, asc or des. Default is des
*/
static flashSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`countingSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
let
max = 0,
min = arr[0],
m = ~~(0.45 * arr.length),
l = new Array(m);
for (let i = 1; i < arr.length; ++i) {
if (arr[i] < min) {
min = arr[i];
}
if (arr[i] > arr[max]) {
max = i;
}
}
if (min === arr[max]) {
return arr;
}
let c1 = (m - 1) / (arr[max] - min);
for (let k = 0; k < m; k++) {
l[k] = 0;
}
for (let j = 0; j < arr.length; ++j) {
k = ~~(c1 * (arr[j] - min));
++l[k];
}
for (let p = 1; p < m; ++p) {
l[p] = l[p] + l[p - 1];
}
SortingAlgorithms.swap(arr, 0, max);
let
move = 0,
t,
flash;
j = 0;
k = m - 1;
while (move < (arr.length - 1)) {
while (j > (l[k] - 1)) {
++j;
k = ~~(c1 * (arr[j] - min));
}
if (k < 0) break;
flash = arr[j];
while (j !== l[k]) {
k = ~~(c1 * (flash - min));
hold = arr[t = --l[k]];
arr[t] = flash;
flash = hold;
++move;
}
}
for (j = 1; j < arr.length; j++) {
hold = arr[j];
i = j - 1;
while (i >= 0 && arr[i] > hold) {
arr[i + 1] = arr[i--];
}
arr[i + 1] = hold;
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using LSD radix sort
* {Stable: Yes, Time: O(n*(k/d)), Memory: O(n+2^d), n<<2^k: No}
* @param {Array} arr Array of numbers
* @param {string} order Sorting order, asc or des. Default is des
*/
static LSDradixSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`LSDradixSort() expects an array! Found ${typeof arr}.`);
}
arr.forEach(number => {
if (isNaN(number)) {
throw new Error(`LSDradixSort() expects an array of numbers! Found ${typeof number}.`);
}
})
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
const max = SortingAlgorithms.getMax(arr);
for (let i = 0; i < max; i++) {
let buckets = Array.from({ length: 10 }, () => []);
for (let j = 0; j < arr.length; j++) {
buckets[SortingAlgorithms.getPosition(arr[j], i)].push(arr[j]);
}
arr = [].concat(...buckets);
}
return order === 'asc'
? arr.reverse()
: arr;
}
/** Returns a sorted array using pigeonhole sort
* {Stable: Yes, Time: O(n+r), Memory: O(2^k), n<<2^k: Yes}
* @param {Array} arr Array of numbers
* @param {string} order Sorting order, asc or des. Default is des
*/
static pigeonholeSort(arr, order = 'des') {
if (!Array.isArray(arr)) {
throw new Error(`pigeonholeSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
const pigeonhole = [];
arr.forEach(number => {
if (isNaN(number)) {
throw new Error(`pigeonholeSort() expects an array of numbers! Found ${typeof number}.`);
}
pigeonhole[number]
? pigeonhole[number].push(number)
: pigeonhole[number] = [number];
});
arr = pigeonhole.reduce((a, b) => a.concat(b), []);
return order === 'asc'
? arr.reverse()
: arr;
}
/*
Other sorts
*/
/** WARNING: BOGO SORT SHOULD NEVER BE USED FOR ACTUAL SORTING ONLY FOR TESTING.
* Returns a sorted array using bogo sort
* {Stable: No, Time: O(n*n!), Memory: O(1)}
* @param {Array} arr Array of numbers or strings
*/
static bogoSort(arr) {
if (!Array.isArray(arr)) {
throw new Error(`bogoSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
const isSorted = arr => {
for (let i = 1; i < arr.length; i++) {
if (arr[i - 1] > arr[i]) {
return false;
}
}
return true;
};
const shuffle = arr => {
let
count = arr.length,
index;
while (count > 0) {
index = Math.floor(Math.random() * count);
count--;
SortingAlgorithms.swap(arr, count, index);
}
return arr;
}
const sort = arr => {
let sorted = false;
while (!sorted) {
arr = shuffle(arr);
sorted = isSorted(arr);
}
return arr;
}
return sort(arr);
}
/** WARNING: GRAVITY SORT SHOULD NEVER BE USED FOR ACTUAL SORTING ONLY FOR TESTING.
* Returns a sorted array using gravity sort
* {Stable: n/a, Time: O(S), Memory: O(n^2)}
* @param {Array} arr Array of numbers or strings'
*/
static gravitySort(arr) {
if (!Array.isArray(arr)) {
throw new Error(`countingSort() expects an array! Found ${typeof arr}.`);
}
if (order.toLowerCase() !== 'des' && order.toLowerCase() !== 'asc') {
order = 'des';
}
let
m = Math.max(...arr),
gravityWell = [];
for (let i = 0; i < arr.length; i++) {
gravityWell.push([]);
for (let j = 0; j < m; j++) {
gravityWell[i][j] = arr[i] > j ? 1 : 0;
}
}
for (let x = 0; x < m; x++) {
let
count = 0,
modIdxs = [];
for (let y = 0; y < gravityWell.length; y++) {
if (gravityWell[y][x] == 1) {
count++;
gravityWell[y][x] = 0;
modIdxs.push(y);
}
}
for (let y = gravityWell.length - 1; y >= gravityWell.length - count; y--) {
gravityWell[y][x] = 1;
modIdxs.push(y);
}
}
const beadsToArray = (gravityWell, arr = []) => {
for (let i = 0; i < gravityWell.length; i++) {
arr[i] = gravityWell[i].reduce((a, b) => a + b);
}
return arr
}
beadsToArray(gravityWell, arr);
return arr;
}
/** WARNING: STOOGE SORT SHOULD NEVER BE USED FOR ACTUAL SORTING ONLY FOR TESTING.
* Returns a sorted array u