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