rusty_source_map/
binary_search.rs

1pub const GREATEST_LOWER_BOUND: i32 = 1;
2pub const LEAST_UPPER_BOUND: i32 = 2;
3
4///
5/// Recursive implementation of binary search.
6///
7/// @param aLow Indices here and lower do not contain the needle.
8/// @param aHigh Indices here and higher do not contain the needle.
9/// @param aNeedle The element being searched for.
10/// @param aHaystack The non-empty array being searched.
11/// @param aCompare Function which takes two elements and returns -1, 0, or 1.
12/// @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
13///     'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
14///     closest element that is smaller than or greater than the one we are
15///     searching for, respectively, if the exact element cannot be found.
16/// ref: https://github.com/mozilla/source-map/blob/58819f09018d56ef84dc41ba9c93f554e0645169/lib/binary-search.js#L24
17fn recursive_search<T1, T2>(
18    low: i32,
19    high: i32,
20    needle: &T1,
21    hay_stack: &[T2],
22    compare: &impl Fn(&T1, &T2) -> i32,
23    bias: i32,
24) -> i32 {
25    // This function terminates when one of the following is true:
26    //
27    //   1. We find the exact element we are looking for.
28    //
29    //   2. We did not find the exact element, but we can return the index of
30    //      the next-closest element.
31    //
32    //   3. We did not find the exact element, and there is no next-closest
33    //      element than the one we are searching for, so we return -1.
34
35    let mid: i32 = (high - low) / 2 + low;
36    let cmp = compare(needle, &hay_stack[mid as usize]);
37
38    #[allow(clippy::comparison_chain)]
39    if cmp == 0 {
40        // Found the element we are looking for.
41        return mid;
42    } else if cmp > 0 {
43        // Our needle is greater than aHaystack[mid].
44        if high - mid > 1 {
45            // The element is in the upper half.
46            return recursive_search(mid, high, needle, hay_stack, compare, bias);
47        }
48        // The exact needle element was not found in this haystack. Determine if
49        // we are in termination case (3) or (2) and return the appropriate thing.
50        if bias == LEAST_UPPER_BOUND {
51            return if high < hay_stack.len() as i32 {
52                high
53            } else {
54                -1
55            };
56        }
57        return mid;
58    }
59
60    // Our needle is less than aHaystack[mid].
61    if mid - low > 1 {
62        return recursive_search(low, mid, needle, hay_stack, compare, bias);
63    }
64
65    // we are in termination case (3) or (2) and return the appropriate thing.
66    if bias == LEAST_UPPER_BOUND {
67        return mid;
68    }
69
70    if low < 0 {
71        -1
72    } else {
73        low
74    }
75}
76
77pub fn search<T1, T2>(
78    needle: T1,
79    hay_stack: &[T2],
80    compare1: impl Fn(&T1, &T2) -> i32,
81    compare2: impl Fn(&T2, &T2) -> i32,
82    bias: Option<i32>,
83) -> i32 {
84    if hay_stack.is_empty() {
85        return -1;
86    }
87
88    let mut index = recursive_search(
89        -1,
90        hay_stack.len() as i32,
91        &needle,
92        hay_stack,
93        &compare1,
94        bias.unwrap_or(GREATEST_LOWER_BOUND),
95    );
96    if index < 0 {
97        return -1;
98    }
99
100    // We have found either the exact element, or the next-closest element to
101    // the one we are searching for. However, there may be more than one such
102    // element. Make sure we always return the smallest of these.
103    while index > 0 {
104        if compare2(&hay_stack[index as usize], &hay_stack[(index - 1) as usize]) != 0 {
105            break;
106        }
107        index -= 1;
108    }
109
110    index
111}
112
113#[cfg(test)]
114mod test {
115    use super::*;
116
117    fn number_cmp(a: &i32, b: &i32) -> i32 {
118        a - b
119    }
120
121    #[test]
122    fn test_too_high_with_default_bias() {
123        let needle = 30;
124        let hay_stack = vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20];
125
126        assert_eq!(
127            hay_stack[search(needle, &hay_stack, number_cmp, number_cmp, None) as usize],
128            20
129        );
130    }
131
132    #[test]
133    fn test_too_low_with_default_bias() {
134        let needle = 1;
135        let hay_stack = vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20];
136        assert_eq!(
137            hay_stack[search(
138                needle,
139                &hay_stack,
140                number_cmp,
141                number_cmp,
142                Some(LEAST_UPPER_BOUND)
143            ) as usize],
144            2
145        )
146    }
147
148    #[test]
149    fn test_exact_search() {
150        let needle = 4;
151        let hay_stack = vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20];
152
153        assert_eq!(
154            hay_stack[search(needle, &hay_stack, number_cmp, number_cmp, None) as usize],
155            4
156        )
157    }
158
159    #[test]
160    fn test_fuzzy_search_with_glb_bias() {
161        let needle = 19;
162        let hay_stack = vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20];
163
164        assert_eq!(
165            hay_stack[search(needle, &hay_stack, number_cmp, number_cmp, None) as usize],
166            18
167        )
168    }
169
170    #[test]
171    fn test_fuzzy_search_with_lub_bias() {
172        let needle = 19;
173        let hay_stack = vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20];
174
175        assert_eq!(
176            hay_stack[search(
177                needle,
178                &hay_stack,
179                number_cmp,
180                number_cmp,
181                Some(LEAST_UPPER_BOUND)
182            ) as usize],
183            20
184        )
185    }
186
187    #[test]
188    fn test_multiple_matches() {
189        let needle = 5;
190        let hay_stack = vec![1, 1, 2, 5, 5, 5, 13, 21];
191        assert_eq!(
192            search(
193                needle,
194                &hay_stack,
195                number_cmp,
196                number_cmp,
197                Some(LEAST_UPPER_BOUND)
198            ),
199            3
200        )
201    }
202
203    #[test]
204    fn test_multiple_matches_at_beginning() {
205        let needle = 1;
206        let hay_stack = vec![1, 1, 2, 5, 5, 5, 13, 21];
207
208        assert_eq!(
209            search(
210                needle,
211                &hay_stack,
212                number_cmp,
213                number_cmp,
214                Some(LEAST_UPPER_BOUND)
215            ),
216            0
217        )
218    }
219}