1pub const GREATEST_LOWER_BOUND: i32 = 1;
2pub const LEAST_UPPER_BOUND: i32 = 2;
3
4fn 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 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 return mid;
42 } else if cmp > 0 {
43 if high - mid > 1 {
45 return recursive_search(mid, high, needle, hay_stack, compare, bias);
47 }
48 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 if mid - low > 1 {
62 return recursive_search(low, mid, needle, hay_stack, compare, bias);
63 }
64
65 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 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}