binarySearchBounds.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. * @template T
  63. * @typedef {(items: T[], start: number, compareFn?: number | ((item: T, needle: number) => number), l?: number, h?: number) => number} Search
  64. */
  65. /**
  66. * This helper functions generate code for two binary search functions:
  67. * A(): Performs a binary search on an array using the comparison operator specified.
  68. * P(): Performs a binary search on an array using a _custom comparison function_
  69. * `c(x,y)` **and** comparison operator specified by `predicate`.
  70. * @template T
  71. * @param {BinarySearchPredicate} predicate The predicate / comparison operator to be used in the binary search.
  72. * @param {boolean} reversed Whether the search should be reversed.
  73. * @param {SearchPredicateSuffix} suffix The suffix to be used in the function name.
  74. * @param {boolean=} earlyOut Whether the search should return as soon as a match is found.
  75. * @returns {Search<T>} The compiled binary search function.
  76. */
  77. const compileBoundsSearch = (predicate, reversed, suffix, earlyOut) => {
  78. const arg1 = compileSearch("A", `x${predicate}y`, reversed, ["y"], earlyOut);
  79. const arg2 = compileSearch(
  80. "P",
  81. `c(x,y)${predicate}0`,
  82. reversed,
  83. ["y", "c"],
  84. earlyOut
  85. );
  86. const fnHeader = "function dispatchBinarySearch";
  87. const fnBody =
  88. // eslint-disable-next-line no-multi-str
  89. "(a,y,c,l,h){\
  90. if(typeof(c)==='function'){\
  91. return P(a,(l===void 0)?0:l|0,(h===void 0)?a.length-1:h|0,y,c)\
  92. }else{\
  93. return A(a,(c===void 0)?0:c|0,(l===void 0)?a.length-1:l|0,y)\
  94. }}\
  95. return dispatchBinarySearch";
  96. const fnArgList = [arg1, arg2, fnHeader, suffix, fnBody, suffix];
  97. const fnSource = fnArgList.join("");
  98. // eslint-disable-next-line no-new-func
  99. const result = new Function(fnSource);
  100. return result();
  101. };
  102. const fns = {
  103. ge: compileBoundsSearch(">=", false, "GE"),
  104. gt: compileBoundsSearch(">", false, "GT"),
  105. lt: compileBoundsSearch("<", true, "LT"),
  106. le: compileBoundsSearch("<=", true, "LE"),
  107. eq: compileBoundsSearch("-", true, "EQ", true)
  108. };
  109. /**
  110. * These functions are used to perform binary searches on arrays.
  111. * @example
  112. * ```js
  113. * const { gt, le} = require("./binarySearchBounds");
  114. * const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  115. *
  116. * // Find the index of the first element greater than 5
  117. * const index1 = gt(arr, 5); // index1 === 3
  118. *
  119. * // Find the index of the first element less than or equal to 5
  120. * const index2 = le(arr, 5); // index2 === 4
  121. * ```
  122. */
  123. module.exports = fns;