binarySearchBounds.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Mikola Lysenko @mikolalysenko
  4. */
  5. "use strict";
  6. /* cspell:disable-next-line */
  7. // Refactor: Peter Somogyvari @petermetz
  8. /** @typedef {">=" | "<=" | "<" | ">" | "-" } BinarySearchPredicate */
  9. /** @typedef {"GE" | "GT" | "LT" | "LE" | "EQ" } SearchPredicateSuffix */
  10. /**
  11. * Helper function for compiling binary search functions.
  12. *
  13. * The generated code uses a while loop to repeatedly divide the search interval
  14. * in half until the desired element is found, or the search interval is empty.
  15. *
  16. * The following is an example of a generated function for calling `compileSearch("P", "c(x,y)<=0", true, ["y", "c"], false)`:
  17. *
  18. * ```js
  19. * function P(a,l,h,y,c){var i=l-1;while(l<=h){var m=(l+h)>>>1,x=a[m];if(c(x,y)<=0){i=m;l=m+1}else{h=m-1}}return i};
  20. * ```
  21. * @param {string} funcName The name of the function to be compiled.
  22. * @param {string} predicate The predicate / comparison operator to be used in the binary search.
  23. * @param {boolean} reversed Whether the search should be reversed.
  24. * @param {string[]} extraArgs Extra arguments to be passed to the function.
  25. * @param {boolean=} earlyOut Whether the search should return as soon as a match is found.
  26. * @returns {string} The compiled binary search function.
  27. */
  28. const compileSearch = (funcName, predicate, reversed, extraArgs, earlyOut) => {
  29. const code = [
  30. "function ",
  31. funcName,
  32. "(a,l,h,",
  33. extraArgs.join(","),
  34. "){",
  35. earlyOut ? "" : "var i=",
  36. reversed ? "l-1" : "h+1",
  37. ";while(l<=h){var m=(l+h)>>>1,x=a[m]"
  38. ];
  39. if (earlyOut) {
  40. if (!predicate.includes("c")) {
  41. code.push(";if(x===y){return m}else if(x<=y){");
  42. } else {
  43. code.push(";var p=c(x,y);if(p===0){return m}else if(p<=0){");
  44. }
  45. } else {
  46. code.push(";if(", predicate, "){i=m;");
  47. }
  48. if (reversed) {
  49. code.push("l=m+1}else{h=m-1}");
  50. } else {
  51. code.push("h=m-1}else{l=m+1}");
  52. }
  53. code.push("}");
  54. if (earlyOut) {
  55. code.push("return -1};");
  56. } else {
  57. code.push("return i};");
  58. }
  59. return code.join("");
  60. };
  61. /**
  62. * This helper functions generate code for two binary search functions:
  63. * A(): Performs a binary search on an array using the comparison operator specified.
  64. * P(): Performs a binary search on an array using a _custom comparison function_
  65. * `c(x,y)` **and** comparison operator specified by `predicate`.
  66. * @param {BinarySearchPredicate} predicate The predicate / comparison operator to be used in the binary search.
  67. * @param {boolean} reversed Whether the search should be reversed.
  68. * @param {SearchPredicateSuffix} suffix The suffix to be used in the function name.
  69. * @param {boolean=} earlyOut Whether the search should return as soon as a match is found.
  70. * @returns {Function} The compiled binary search function.
  71. */
  72. const compileBoundsSearch = (predicate, reversed, suffix, earlyOut) => {
  73. const arg1 = compileSearch("A", `x${predicate}y`, reversed, ["y"], earlyOut);
  74. const arg2 = compileSearch(
  75. "P",
  76. `c(x,y)${predicate}0`,
  77. reversed,
  78. ["y", "c"],
  79. earlyOut
  80. );
  81. const fnHeader = "function dispatchBinarySearch";
  82. const fnBody =
  83. // eslint-disable-next-line no-multi-str
  84. "(a,y,c,l,h){\
  85. if(typeof(c)==='function'){\
  86. return P(a,(l===void 0)?0:l|0,(h===void 0)?a.length-1:h|0,y,c)\
  87. }else{\
  88. return A(a,(c===void 0)?0:c|0,(l===void 0)?a.length-1:l|0,y)\
  89. }}\
  90. return dispatchBinarySearch";
  91. const fnArgList = [arg1, arg2, fnHeader, suffix, fnBody, suffix];
  92. const fnSource = fnArgList.join("");
  93. // eslint-disable-next-line no-new-func
  94. const result = new Function(fnSource);
  95. return result();
  96. };
  97. /**
  98. * These functions are used to perform binary searches on arrays.
  99. * @example
  100. * ```js
  101. * const { gt, le} = require("./binarySearchBounds");
  102. * const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  103. *
  104. * // Find the index of the first element greater than 5
  105. * const index1 = gt(arr, 5); // index1 === 3
  106. *
  107. * // Find the index of the first element less than or equal to 5
  108. * const index2 = le(arr, 5); // index2 === 4
  109. * ```
  110. */
  111. module.exports = {
  112. ge: compileBoundsSearch(">=", false, "GE"),
  113. gt: compileBoundsSearch(">", false, "GT"),
  114. lt: compileBoundsSearch("<", true, "LT"),
  115. le: compileBoundsSearch("<=", true, "LE"),
  116. eq: compileBoundsSearch("-", true, "EQ", true)
  117. };