[d565449] | 1 | /* -*- Mode: js; js-indent-level: 2; -*- */
|
---|
| 2 | /*
|
---|
| 3 | * Copyright 2011 Mozilla Foundation and contributors
|
---|
| 4 | * Licensed under the New BSD license. See LICENSE or:
|
---|
| 5 | * http://opensource.org/licenses/BSD-3-Clause
|
---|
| 6 | */
|
---|
| 7 |
|
---|
| 8 | // It turns out that some (most?) JavaScript engines don't self-host
|
---|
| 9 | // `Array.prototype.sort`. This makes sense because C++ will likely remain
|
---|
| 10 | // faster than JS when doing raw CPU-intensive sorting. However, when using a
|
---|
| 11 | // custom comparator function, calling back and forth between the VM's C++ and
|
---|
| 12 | // JIT'd JS is rather slow *and* loses JIT type information, resulting in
|
---|
| 13 | // worse generated code for the comparator function than would be optimal. In
|
---|
| 14 | // fact, when sorting with a comparator, these costs outweigh the benefits of
|
---|
| 15 | // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
|
---|
| 16 | // a ~3500ms mean speed-up in `bench/bench.html`.
|
---|
| 17 |
|
---|
| 18 | function SortTemplate(comparator) {
|
---|
| 19 |
|
---|
| 20 | /**
|
---|
| 21 | * Swap the elements indexed by `x` and `y` in the array `ary`.
|
---|
| 22 | *
|
---|
| 23 | * @param {Array} ary
|
---|
| 24 | * The array.
|
---|
| 25 | * @param {Number} x
|
---|
| 26 | * The index of the first item.
|
---|
| 27 | * @param {Number} y
|
---|
| 28 | * The index of the second item.
|
---|
| 29 | */
|
---|
| 30 | function swap(ary, x, y) {
|
---|
| 31 | var temp = ary[x];
|
---|
| 32 | ary[x] = ary[y];
|
---|
| 33 | ary[y] = temp;
|
---|
| 34 | }
|
---|
| 35 |
|
---|
| 36 | /**
|
---|
| 37 | * Returns a random integer within the range `low .. high` inclusive.
|
---|
| 38 | *
|
---|
| 39 | * @param {Number} low
|
---|
| 40 | * The lower bound on the range.
|
---|
| 41 | * @param {Number} high
|
---|
| 42 | * The upper bound on the range.
|
---|
| 43 | */
|
---|
| 44 | function randomIntInRange(low, high) {
|
---|
| 45 | return Math.round(low + (Math.random() * (high - low)));
|
---|
| 46 | }
|
---|
| 47 |
|
---|
| 48 | /**
|
---|
| 49 | * The Quick Sort algorithm.
|
---|
| 50 | *
|
---|
| 51 | * @param {Array} ary
|
---|
| 52 | * An array to sort.
|
---|
| 53 | * @param {function} comparator
|
---|
| 54 | * Function to use to compare two items.
|
---|
| 55 | * @param {Number} p
|
---|
| 56 | * Start index of the array
|
---|
| 57 | * @param {Number} r
|
---|
| 58 | * End index of the array
|
---|
| 59 | */
|
---|
| 60 | function doQuickSort(ary, comparator, p, r) {
|
---|
| 61 | // If our lower bound is less than our upper bound, we (1) partition the
|
---|
| 62 | // array into two pieces and (2) recurse on each half. If it is not, this is
|
---|
| 63 | // the empty array and our base case.
|
---|
| 64 |
|
---|
| 65 | if (p < r) {
|
---|
| 66 | // (1) Partitioning.
|
---|
| 67 | //
|
---|
| 68 | // The partitioning chooses a pivot between `p` and `r` and moves all
|
---|
| 69 | // elements that are less than or equal to the pivot to the before it, and
|
---|
| 70 | // all the elements that are greater than it after it. The effect is that
|
---|
| 71 | // once partition is done, the pivot is in the exact place it will be when
|
---|
| 72 | // the array is put in sorted order, and it will not need to be moved
|
---|
| 73 | // again. This runs in O(n) time.
|
---|
| 74 |
|
---|
| 75 | // Always choose a random pivot so that an input array which is reverse
|
---|
| 76 | // sorted does not cause O(n^2) running time.
|
---|
| 77 | var pivotIndex = randomIntInRange(p, r);
|
---|
| 78 | var i = p - 1;
|
---|
| 79 |
|
---|
| 80 | swap(ary, pivotIndex, r);
|
---|
| 81 | var pivot = ary[r];
|
---|
| 82 |
|
---|
| 83 | // Immediately after `j` is incremented in this loop, the following hold
|
---|
| 84 | // true:
|
---|
| 85 | //
|
---|
| 86 | // * Every element in `ary[p .. i]` is less than or equal to the pivot.
|
---|
| 87 | //
|
---|
| 88 | // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
|
---|
| 89 | for (var j = p; j < r; j++) {
|
---|
| 90 | if (comparator(ary[j], pivot, false) <= 0) {
|
---|
| 91 | i += 1;
|
---|
| 92 | swap(ary, i, j);
|
---|
| 93 | }
|
---|
| 94 | }
|
---|
| 95 |
|
---|
| 96 | swap(ary, i + 1, j);
|
---|
| 97 | var q = i + 1;
|
---|
| 98 |
|
---|
| 99 | // (2) Recurse on each half.
|
---|
| 100 |
|
---|
| 101 | doQuickSort(ary, comparator, p, q - 1);
|
---|
| 102 | doQuickSort(ary, comparator, q + 1, r);
|
---|
| 103 | }
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | return doQuickSort;
|
---|
| 107 | }
|
---|
| 108 |
|
---|
| 109 | function cloneSort(comparator) {
|
---|
| 110 | let template = SortTemplate.toString();
|
---|
| 111 | let templateFn = new Function(`return ${template}`)();
|
---|
| 112 | return templateFn(comparator);
|
---|
| 113 | }
|
---|
| 114 |
|
---|
| 115 | /**
|
---|
| 116 | * Sort the given array in-place with the given comparator function.
|
---|
| 117 | *
|
---|
| 118 | * @param {Array} ary
|
---|
| 119 | * An array to sort.
|
---|
| 120 | * @param {function} comparator
|
---|
| 121 | * Function to use to compare two items.
|
---|
| 122 | */
|
---|
| 123 |
|
---|
| 124 | let sortCache = new WeakMap();
|
---|
| 125 | exports.quickSort = function (ary, comparator, start = 0) {
|
---|
| 126 | let doQuickSort = sortCache.get(comparator);
|
---|
| 127 | if (doQuickSort === void 0) {
|
---|
| 128 | doQuickSort = cloneSort(comparator);
|
---|
| 129 | sortCache.set(comparator, doQuickSort);
|
---|
| 130 | }
|
---|
| 131 | doQuickSort(ary, comparator, start, ary.length - 1);
|
---|
| 132 | };
|
---|