ibowankenobi-mergesort
Version:
Merge Sort algorithm implementation without recursion, using cached binary trees
359 lines (295 loc) • 9.18 kB
JavaScript
/**
* Mergesort module
* @module Mergesort
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Mergesort = factory());
}(this, (function () { 'use strict';
var temp = [];
function sort(arr, l1, l2, f) {
var o = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
var i = 0,
j = 0,
s1 = o,
e1 = o + l1 - 1,
s2 = e1 + 1,
e2 = s2 + l2 - 1,
state = "both not traversed";
if (f(arr[e1], arr[s2]) <= 0) {
return;
} else if (f(arr[s1], arr[e2]) >= 0) {
while (l2--) {
temp[i++] = arr[s2++];
}
while (l1--) {
temp[i++] = arr[s1++];
}
state = "skip";
}
outer: while (state) {
switch (state) {
case "both not traversed":
if (f(arr[s1], arr[s2]) > 0) {
temp[i++] = arr[s2++];
} else {
temp[i++] = arr[s1++];
}
if (s1 > e1) {
state = "left traversed";
continue outer;
} else if (s2 > e2) {
state = "right traversed";
continue outer;
}
continue outer;
case "left traversed":
temp[i++] = arr[s2++];
if (s2 > e2) {
break outer;
}
continue outer;
case "right traversed":
temp[i++] = arr[s1++];
if (s1 > e1) {
break outer;
}
continue outer;
case "skip":
break outer;
}
}
while (i--) {
arr[o++] = temp[j++];
}
}
function insertionSort(arr, l, f) {
var o = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
var i = 0,
j,
u,
temp;
--l;
while (i < l) {
j = i;
u = o + j;
temp = arr[u + 1];
while (~j && f(arr[u], temp) > 0) {
arr[u + 1] = arr[u];
--j, --u;
}
arr[u + 1] = temp;
i++;
}
return arr;
}
function walk(arr, tree, f) {
var hThreshold = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 8;
var backtrack = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
outer: while (tree) {
var leaf = tree.firstChild;
if (!leaf) {
if (tree.p) {
tree = tree.p;
continue;
}
tree = null;
continue;
}
while (leaf.firstChild) {
leaf = leaf.firstChild;
}
if (leaf.nextSibling.firstChild) {
tree = leaf.nextSibling;
continue;
}
if (leaf.l <= hThreshold) {
insertionSort(arr, leaf.p.l, f, leaf.o);
} else {
sort(arr, leaf.l, leaf.nextSibling.l, f, leaf.o);
}
leaf = leaf.p;
backtrack && (leaf.lastChild = leaf.firstChild.nextSibling);
leaf.firstChild = null;
while (!leaf.nextSibling) {
leaf = leaf.p || leaf;
if (!leaf.p) {
tree = leaf;
continue outer;
}
}
tree = leaf.nextSibling;
}
}
function tree(l) {
var node = {
o: 0,
l: l,
p: null,
nextSibling: null,
prevSibling: null
},
state = "down";
outer: while (state) {
switch (state) {
case "down":
if (node.l <= 1) {
state = "right";
continue outer;
}
var nL = node.l / 2 | 0;
node.firstChild = {
o: node.o,
l: nL,
p: node,
nextSibling: null,
prevSibling: null
};
node.firstChild.nextSibling = node.lastChild = {
o: node.o + nL,
l: node.l - nL,
p: node,
nextSibling: null,
prevSibling: node.firstChild
};
node = node.firstChild;
state = "down";
continue outer;
case "right":
if (!node.nextSibling) {
state = "up";
continue outer;
}
node = node.nextSibling;
state = "down";
continue outer;
case "up":
if (!node.p) {
return node;
}
node = node.p;
if (node.nextSibling) {
state = "right";
continue outer;
}
state = "up";
continue outer;
}
}
}
function regen(tree) {
outer: while (tree) {
var leaf = tree.lastChild;
if (!leaf) {
if (tree.p) {
tree = tree.p;
continue;
}
tree = null;
continue;
}
while (leaf.lastChild) {
leaf = leaf.lastChild;
}
if (leaf.prevSibling.lastChild) {
tree = leaf.prevSibling;
continue;
}
leaf = leaf.p;
leaf.firstChild = leaf.lastChild.prevSibling;
leaf.lastChild = null;
while (!leaf.prevSibling) {
leaf = leaf.p || leaf;
if (!leaf.p) {
tree = leaf;
continue outer;
}
}
tree = leaf.prevSibling;
}
}
/**
* An optinal configuration object that can be passed to the `Mergesort` factory function
* @typedef {Object} module:Mergesort~config
* @property {number} threshold The threshold below where the algorithm temporarily switches over to insertion sort
* @property {number} size Use this if you are going to consistently sort arrays of fixed size.
* Refers to size of the array to be sorted, where a binary tree is precalculated.
* The tree will be reused for each call of the returned `instance`.
* For each use, the tree is *walked* by setting `firstChild` properties of leaf nodes to null
* and then regenerated by setting them again from the `lastChild` property of their parent.
* This option results in a performance gain for large (> 1M) arrays, where cost of creating
* the tree is greater than the cost of walking + regenerating the tree. For small arrays,
* it has the reverse effect. It is set to off by default.
*/
var undef = void 0;
/**
* Returns a `Mergesort` `instance`
*
* ```javascript
*
* let instance = Mergesort(); //switches to insertion sort for array fragments < 16
*
* instance = Mergesort({threshold: 64}); //will switch to insertion sort for fragments < 64
*
* instance = Mergesort({size:999}); //will throw an error if you try to sort arrays with length other than 999
*
* ```
*
* @alias module:Mergesort
*
* @param {module:Mergesort~config} config a configuration object with optional parameters (default 16)
* @returns {module:Mergesort~instance}
*
*/
function index () {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
threshold: 16,
size: undef
},
_ref$threshold = _ref.threshold,
threshold = _ref$threshold === void 0 ? 16 : _ref$threshold,
_ref$size = _ref.size,
size = _ref$size === void 0 ? undef : _ref$size;
if (size && typeof size !== "number") {
throw new Error("Size must be of type number");
}
var hThreshold = threshold / 2,
_tree = size ? tree(size) : undef,
/**
*
* ```javascript
* let instance = Mergesort(),
*
* inputArray = [{value:10},{value:1},{value:5}],
*
* compare = (a, b) => a.value - b.value;
*
* instance(inputArray, compare); //[{value:1}, ...]
*
* @namespace
* @param {Array} ArrayToBeSorted - Input array to be sorted
* @param {Function} Compare - compare function, same as in [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
* @returns {Array} input array
*/
instance = function instance(arr, f) {
var _length = arr.length;
if (_tree) {
if (_length !== _tree.l) {
throw new Error("Array length must be " + size);
}
temp.length = _length;
walk(arr, _tree, f, hThreshold, true);
regen(_tree);
} else {
temp.length = _length;
walk(arr, tree(_length), f, hThreshold);
}
temp.length = 0;
return arr;
};
return instance;
}
index.version = "0.0.3";
return index;
})));