binarySearchBounds.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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. *
  22. * @param {string} funcName The name of the function to be compiled.
  23. * @param {string} predicate The predicate / comparison operator to be used in the binary search.
  24. * @param {boolean} reversed Whether the search should be reversed.
  25. * @param {string[]} extraArgs Extra arguments to be passed to the function.
  26. * @param {boolean=} earlyOut Whether the search should return as soon as a match is found.
  27. * @returns {string} The compiled binary search function.
  28. */
  29. const compileSearch = (funcName, predicate, reversed, extraArgs, earlyOut) => {
  30. const code = [
  31. "function ",
  32. funcName,
  33. "(a,l,h,",
  34. extraArgs.join(","),
  35. "){",
  36. earlyOut ? "" : "var i=",
  37. reversed ? "l-1" : "h+1",
  38. ";while(l<=h){var m=(l+h)>>>1,x=a[m]"
  39. ];
  40. if (earlyOut) {
  41. if (predicate.indexOf("c") < 0) {
  42. code.push(";if(x===y){return m}else if(x<=y){");
  43. } else {
  44. code.push(";var p=c(x,y);if(p===0){return m}else if(p<=0){");
  45. }
  46. } else {
  47. code.push(";if(", predicate, "){i=m;");
  48. }
  49. if (reversed) {
  50. code.push("l=m+1}else{h=m-1}");
  51. } else {
  52. code.push("h=m-1}else{l=m+1}");
  53. }
  54. code.push("}");
  55. if (earlyOut) {
  56. code.push("return -1};");
  57. } else {
  58. code.push("return i};");
  59. }
  60. return code.join("");
  61. };
  62. /**
  63. * This helper functions generate code for two binary search functions:
  64. * A(): Performs a binary search on an array using the comparison operator specified.
  65. * P(): Performs a binary search on an array using a _custom comparison function_
  66. * `c(x,y)` **and** comparison operator specified by `predicate`.
  67. *
  68. * @param {BinarySearchPredicate} predicate The predicate / comparison operator to be used in the binary search.
  69. * @param {boolean} reversed Whether the search should be reversed.
  70. * @param {SearchPredicateSuffix} suffix The suffix to be used in the function name.
  71. * @param {boolean=} earlyOut Whether the search should return as soon as a match is found.
  72. * @returns {Function} The compiled binary search function.
  73. */
  74. const compileBoundsSearch = (predicate, reversed, suffix, earlyOut) => {
  75. const arg1 = compileSearch(
  76. "A",
  77. "x" + predicate + "y",
  78. reversed,
  79. ["y"],
  80. earlyOut
  81. );
  82. const arg2 = compileSearch(
  83. "P",
  84. "c(x,y)" + predicate + "0",
  85. reversed,
  86. ["y", "c"],
  87. earlyOut
  88. );
  89. const fnHeader = "function dispatchBinarySearch";
  90. const fnBody =
  91. "(a,y,c,l,h){\
  92. if(typeof(c)==='function'){\
  93. return P(a,(l===void 0)?0:l|0,(h===void 0)?a.length-1:h|0,y,c)\
  94. }else{\
  95. return A(a,(c===void 0)?0:c|0,(l===void 0)?a.length-1:l|0,y)\
  96. }}\
  97. return dispatchBinarySearch";
  98. const fnArgList = [arg1, arg2, fnHeader, suffix, fnBody, suffix];
  99. const fnSource = fnArgList.join("");
  100. const result = new Function(fnSource);
  101. return result();
  102. };
  103. /**
  104. * These functions are used to perform binary searches on arrays.
  105. *
  106. * @example
  107. * ```js
  108. * const { gt, le} = require("./binarySearchBounds");
  109. * const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  110. *
  111. * // Find the index of the first element greater than 5
  112. * const index1 = gt(arr, 5); // index1 === 3
  113. *
  114. * // Find the index of the first element less than or equal to 5
  115. * const index2 = le(arr, 5); // index2 === 4
  116. * ```
  117. */
  118. module.exports = {
  119. ge: compileBoundsSearch(">=", false, "GE"),
  120. gt: compileBoundsSearch(">", false, "GT"),
  121. lt: compileBoundsSearch("<", true, "LT"),
  122. le: compileBoundsSearch("<=", true, "LE"),
  123. eq: compileBoundsSearch("-", true, "EQ", true)
  124. };