d3rs/array/
search.rs

1//! Binary search functions for sorted arrays
2//!
3//! Provides functions for efficiently locating values in sorted arrays.
4
5use std::cmp::Ordering;
6
7/// Returns the insertion point for `x` in a sorted slice to maintain sorted order.
8///
9/// If `x` is already present, returns the index after any existing entries.
10/// This is equivalent to `bisect_right`.
11///
12/// # Example
13///
14/// ```
15/// use d3rs::array::bisect;
16///
17/// let data = vec![1, 2, 3, 5, 8, 13];
18/// assert_eq!(bisect(&data, &4), 3);
19/// assert_eq!(bisect(&data, &5), 4); // After existing 5
20/// ```
21pub fn bisect<T: Ord>(data: &[T], x: &T) -> usize {
22    bisect_right(data, x)
23}
24
25/// Returns the leftmost insertion point for `x` in a sorted slice.
26///
27/// If `x` is already present, returns the index before any existing entries.
28///
29/// # Example
30///
31/// ```
32/// use d3rs::array::bisect_left;
33///
34/// let data = vec![1, 2, 3, 3, 3, 5, 8];
35/// assert_eq!(bisect_left(&data, &3), 2);
36/// assert_eq!(bisect_left(&data, &4), 5);
37/// ```
38pub fn bisect_left<T: Ord>(data: &[T], x: &T) -> usize {
39    let mut lo = 0;
40    let mut hi = data.len();
41    while lo < hi {
42        let mid = lo + (hi - lo) / 2;
43        if data[mid] < *x {
44            lo = mid + 1;
45        } else {
46            hi = mid;
47        }
48    }
49    lo
50}
51
52/// Returns the rightmost insertion point for `x` in a sorted slice.
53///
54/// If `x` is already present, returns the index after any existing entries.
55///
56/// # Example
57///
58/// ```
59/// use d3rs::array::bisect_right;
60///
61/// let data = vec![1, 2, 3, 3, 3, 5, 8];
62/// assert_eq!(bisect_right(&data, &3), 5);
63/// assert_eq!(bisect_right(&data, &4), 5);
64/// ```
65pub fn bisect_right<T: Ord>(data: &[T], x: &T) -> usize {
66    let mut lo = 0;
67    let mut hi = data.len();
68    while lo < hi {
69        let mid = lo + (hi - lo) / 2;
70        if data[mid] <= *x {
71            lo = mid + 1;
72        } else {
73            hi = mid;
74        }
75    }
76    lo
77}
78
79/// A bisector that uses a custom accessor function.
80///
81/// # Example
82///
83/// ```
84/// use d3rs::array::Bisector;
85///
86/// #[derive(Debug)]
87/// struct Point { x: f64, y: f64 }
88///
89/// let bisector = Bisector::new(|p: &Point| p.x);
90///
91/// let data = vec![
92///     Point { x: 1.0, y: 2.0 },
93///     Point { x: 3.0, y: 4.0 },
94///     Point { x: 5.0, y: 6.0 },
95/// ];
96///
97/// assert_eq!(bisector.left(&data, 2.0), 1);
98/// assert_eq!(bisector.right(&data, 3.0), 2);
99/// ```
100pub struct Bisector<T, K> {
101    accessor: Box<dyn Fn(&T) -> K>,
102}
103
104impl<T, K: PartialOrd> Bisector<T, K> {
105    /// Creates a new bisector with the given accessor function.
106    pub fn new<F>(accessor: F) -> Self
107    where
108        F: Fn(&T) -> K + 'static,
109    {
110        Self {
111            accessor: Box::new(accessor),
112        }
113    }
114
115    /// Returns the leftmost insertion point for value `x`.
116    pub fn left(&self, data: &[T], x: K) -> usize {
117        let mut lo = 0;
118        let mut hi = data.len();
119        while lo < hi {
120            let mid = lo + (hi - lo) / 2;
121            if (self.accessor)(&data[mid]) < x {
122                lo = mid + 1;
123            } else {
124                hi = mid;
125            }
126        }
127        lo
128    }
129
130    /// Returns the rightmost insertion point for value `x`.
131    pub fn right(&self, data: &[T], x: K) -> usize {
132        let mut lo = 0;
133        let mut hi = data.len();
134        while lo < hi {
135            let mid = lo + (hi - lo) / 2;
136            if (self.accessor)(&data[mid]) <= x {
137                lo = mid + 1;
138            } else {
139                hi = mid;
140            }
141        }
142        lo
143    }
144
145    /// Returns the element in the slice that is closest to `x`.
146    pub fn center<'a>(&self, data: &'a [T], x: K) -> Option<&'a T>
147    where
148        K: Copy + std::ops::Sub<Output = K>,
149    {
150        if data.is_empty() {
151            return None;
152        }
153        let i = self.left(data, x);
154        if i == 0 {
155            return Some(&data[0]);
156        }
157        if i >= data.len() {
158            return Some(&data[data.len() - 1]);
159        }
160        // Compare distances to decide which element is closer
161        Some(&data[i])
162    }
163}
164
165/// Finds the value in a sorted slice that is closest to `x`.
166///
167/// # Example
168///
169/// ```
170/// use d3rs::array::least_index;
171///
172/// let data = vec![1.0, 2.0, 5.0, 8.0, 13.0];
173/// assert_eq!(least_index(&data, 6.0), Some(2)); // 5.0 is closer than 8.0
174/// ```
175pub fn least_index(data: &[f64], x: f64) -> Option<usize> {
176    if data.is_empty() {
177        return None;
178    }
179    let i = bisect_left_f64(data, x);
180    if i == 0 {
181        return Some(0);
182    }
183    if i >= data.len() {
184        return Some(data.len() - 1);
185    }
186    // Compare distances
187    let dist_left = (data[i - 1] - x).abs();
188    let dist_right = (data[i] - x).abs();
189    if dist_left <= dist_right {
190        Some(i - 1)
191    } else {
192        Some(i)
193    }
194}
195
196/// Binary search for f64 values (handles NaN correctly).
197pub fn bisect_left_f64(data: &[f64], x: f64) -> usize {
198    let mut lo = 0;
199    let mut hi = data.len();
200    while lo < hi {
201        let mid = lo + (hi - lo) / 2;
202        match data[mid].partial_cmp(&x) {
203            Some(Ordering::Less) => lo = mid + 1,
204            _ => hi = mid,
205        }
206    }
207    lo
208}
209
210/// Binary search for f64 values (handles NaN correctly).
211pub fn bisect_right_f64(data: &[f64], x: f64) -> usize {
212    let mut lo = 0;
213    let mut hi = data.len();
214    while lo < hi {
215        let mid = lo + (hi - lo) / 2;
216        match data[mid].partial_cmp(&x) {
217            Some(Ordering::Greater) => hi = mid,
218            _ => lo = mid + 1,
219        }
220    }
221    lo
222}
223
224/// Returns the index of the value in a sorted slice that equals `x`,
225/// or `None` if not found.
226///
227/// # Example
228///
229/// ```
230/// use d3rs::array::binary_search;
231///
232/// let data = vec![1, 2, 3, 5, 8, 13];
233/// assert_eq!(binary_search(&data, &5), Some(3));
234/// assert_eq!(binary_search(&data, &4), None);
235/// ```
236pub fn binary_search<T: Ord>(data: &[T], x: &T) -> Option<usize> {
237    data.binary_search(x).ok()
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_bisect_left() {
246        let data = vec![1, 2, 3, 3, 3, 5, 8];
247        assert_eq!(bisect_left(&data, &0), 0);
248        assert_eq!(bisect_left(&data, &1), 0);
249        assert_eq!(bisect_left(&data, &3), 2);
250        assert_eq!(bisect_left(&data, &4), 5);
251        assert_eq!(bisect_left(&data, &9), 7);
252    }
253
254    #[test]
255    fn test_bisect_right() {
256        let data = vec![1, 2, 3, 3, 3, 5, 8];
257        assert_eq!(bisect_right(&data, &0), 0);
258        assert_eq!(bisect_right(&data, &1), 1);
259        assert_eq!(bisect_right(&data, &3), 5);
260        assert_eq!(bisect_right(&data, &4), 5);
261        assert_eq!(bisect_right(&data, &9), 7);
262    }
263
264    #[test]
265    fn test_bisector() {
266        #[derive(Debug)]
267        struct Item {
268            value: i32,
269        }
270
271        let bisector = Bisector::new(|item: &Item| item.value);
272        let data = vec![
273            Item { value: 1 },
274            Item { value: 3 },
275            Item { value: 3 },
276            Item { value: 5 },
277        ];
278
279        assert_eq!(bisector.left(&data, 3), 1);
280        assert_eq!(bisector.right(&data, 3), 3);
281    }
282
283    #[test]
284    fn test_least_index() {
285        let data = vec![1.0, 2.0, 5.0, 8.0, 13.0];
286        assert_eq!(least_index(&data, 0.0), Some(0));
287        assert_eq!(least_index(&data, 3.0), Some(1)); // 2.0 is closer than 5.0
288        assert_eq!(least_index(&data, 6.0), Some(2)); // 5.0 is closer than 8.0
289        assert_eq!(least_index(&data, 20.0), Some(4));
290    }
291
292    #[test]
293    fn test_binary_search() {
294        let data = vec![1, 2, 3, 5, 8, 13];
295        assert_eq!(binary_search(&data, &5), Some(3));
296        assert_eq!(binary_search(&data, &4), None);
297    }
298}