/** * @function Introsort (As implemented in STD C++ Lib) * The function performs introsort which is used in * C++ Standard LIbrary, the implementation is inspired from] * library routine itself. * ALGORITHM: * 1) It performs quicksort on array until the recursion depth * exceeds a pre determined limit. * 2) If the limit is reached it switches to heapsort * 3) For array size less than a threshold(16) directly * does insertion sort on array * @param {Array} array the array to be sorted * @param {Function} compare the comparison function * * @see [Introsort](https://en.wikipedia.org/wiki/Introsort) * @author [Lakhan Nad](https://github.com/Lakhan-Nad) */ function introsort(array, compare) { /** * @function Default Comparison Function * This function is same as implemented by * Array.sort method * @see [StackOverflow](https://stackoverflow.com/questions/47334234/how-to-implement-array-prototype-sort-default-compare-function) * @param {*} a variable 1 * @param {*} b variable 2 * @returns {Number} * -1 if a is less than b * 0 if a is equal to b * 1 if a greater than b */ const defaultComparator = function (x, y) { if (x === undefined && y === undefined) return 0 if (x === undefined) return 1 if (y === undefined) return -1 const xString = toString(x) const yString = toString(y) if (xString < yString) return -1 if (xString > yString) return 1 return 0 } /** * @function helper function for defaultComparator * Converts a given object to String * @throws TypeError() * @param {Object} obj * @returns {String} String representation of given object */ const toString = function (obj) { if (obj === null) return 'null' if (typeof obj === 'boolean' || typeof obj === 'number') { return obj.toString() } if (typeof obj === 'string') return obj if (typeof obj === 'symbol') throw new TypeError() return obj.toString() } /** * Checks if the value passed is an array * or not */ if (Array.isArray(array) === false) { return } /** * If the compare parameter is not a function * or not passed at all use default comparator * function */ if (typeof compare !== 'function') { compare = defaultComparator // If compare is not a comparator function } /** * Use a closure to define the whole sort * implementation this is done through * [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) */ return (function (array, comparator) { const swap = function (index1, index2) { const temp = array[index1] array[index1] = array[index2] array[index2] = temp } /** * @constant THRESHOLD * If the length of array is less than * this then we simply perform insertion sort */ const THRESHOLD = 16 /** * @constant TUNEMAXDEPTH * Constant used to increase or decrease value * of maxDepth */ const TUNEMAXDEPTH = 1 const len = array.length /** * Return if array is only of length 1 * Array of size 1 is always sorted */ if (len === 1) { return } /** * Calculate maxDepth = log2(len) * Taken from implementation in stdc++ */ const maxDepth = Math.floor(Math.log2(len)) * TUNEMAXDEPTH /** * The very first call to quicksort * this initiates sort routine */ quickSort(0, len, maxDepth) /** * A final check call to insertion sort * on sorted array */ insertionSort(0, len) /** ********************* Implementation of various routines **************************/ /** * @function * This is recursive quicksort implementation in array * of segment [start,last-1] * [QuickSort](https://en.wikipedia.org/wiki/Quicksort) * @param {Number} start the start index of array segment to be sorted * @param {Number} last one more than the last index of array segment * @param {Number} depth this measures how many recursive calls are done */ function quickSort(start, last, depth) { if (last - start <= THRESHOLD) { insertionSort(start, last) return } else if (depth <= 0) { heapSort(start, last) return } let pivot = (last + start) >> 1 pivot = partition(start, last, pivot) quickSort(start, pivot, depth - 1) quickSort(pivot + 1, last, depth - 1) } /** * @function Helper function to quicksort * @param {Number} start the start of array segment to partition * @param {Number} last one more than last index of the array segment * @param {Number} pivot the index of pivot to be used * @returns {Number} the index of pivot after partition */ function partition(start, last, pivot) { swap(start, pivot) pivot = start let lo = start let hi = last while (true) { lo++ while (comparator(array[lo], array[pivot]) <= 0 && lo !== last) { lo++ } hi-- while (comparator(array[hi], array[pivot]) > 0 && hi !== start) { hi-- } if (lo >= hi) { break } swap(lo, hi) } swap(start, hi) return hi } /** * @function * Performs insertion sort on array of range * [start, last-1] * @param {Number} start the first index of array segment to be sorted * @param {Number} last one more than last index of array to be sorted */ function insertionSort(start, last) { let i, j for (i = start + 1; i < last; i++) { j = i - 1 while (j >= 0 && comparator(array[j], array[j + 1]) > 0) { swap(j, j + 1) j-- } } } /** * @function * Performs heapsort in array segment of range [start, last-1] * [HeapSort](https://en.wikipedia.org/wiki/Heapsort) * @param {Number} start the first index of array segment to be sorted * @param {Number} last one more than last index of array to be sorted */ function heapSort(start, last) { let x = (last + start) >> 1 while (x - start >= 0) { heapify(x, start, last) x-- } x = last - 1 while (x - start > 0) { swap(start, x) heapify(start, start, x) x-- } } /** * @function Helper function to heapsort routine * @param {Number} cur the index we need to heapify * @param {Number} start the start index of array segment that cur belongs to * @param {Number} last one more than last index of segment that cur belongs to */ function heapify(cur, start, last) { const size = last - start let max, lt, rt cur = cur - start while (true) { max = cur lt = 2 * max + 1 rt = 2 * max + 2 if ( lt < size && comparator(array[start + max], array[start + lt]) < 0 ) { max = lt } if ( rt < size && comparator(array[start + max], array[start + rt]) < 0 ) { max = rt } if (max !== cur) { swap(start + cur, start + max) cur = max } else { break } } } })(array, compare) } /** * @example Demo run of the sort routine * The data is randomly generated * Returns 'RIGHT:)' if the sort routine worked as expected, * 'WRONG!!' otherwise */ function demo1() { const data = [] const size = 1000000 let i = 0 let temp const c = function (a, b) { return a - b } for (i = 0; i < size; i++) { temp = Math.random() * Number.MAX_SAFE_INTEGER data.push(temp) } introsort(data, c) let faulty = false for (i = 1; i < size; i++) { if (data[i] < data[i - 1]) { faulty = true break } } if (faulty) { return 'WRONG!!' } else { return 'RIGHT:)' } } /** * @example Demo run of the sort routine * using the default compare function and * comparing the results with Array.sort */ function demo2() { const data = [] const data2 = [] const size = 1000000 let i = 0 let temp for (i = 0; i < size; i++) { temp = Math.random() * Number.MAX_SAFE_INTEGER data.push(temp) data2.push(temp) } introsort(data) data2.sort() let faulty = false for (i = 1; i < size; i++) { if (data[i] !== data2[i]) { faulty = true break } } if (faulty) { return 'WRONG Implemented Comparator!!' } else { return 'Comparator Works Fine:)' } } export { introsort, demo1, demo2 }