quick-sort.js 4.0 KB

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