slice_search/
lib.rs

1#![no_std]
2
3//! A collection of algorithms for searching within slices.
4//!
5//! This module provides different search strategies and utilities to work with sorted slices.
6//! Currently, it supports binary and linear search algorithms, as well as an optimal search
7//! algorithm which picks between binary and linear searches depending on the size of the slice.
8#![deny(missing_docs)]
9
10/// Returns the index of the smallest element greater than or equal to the search
11/// key.
12///
13/// # Example
14/// ```
15/// use slice_search::*;
16///
17/// let array = [0, 1, 2, 3, 4, 6];
18///
19/// let search_result = BinarySearch::search(&array[..], &5);
20/// assert_eq!(upper_bound(search_result, 6), Some(5));
21///
22/// let search_result = BinarySearch::search(&array[..], &10);
23/// assert_eq!(upper_bound(search_result, 6), None);
24/// ```
25pub fn upper_bound(search: Result<usize, usize>, cap: usize) -> Option<usize> {
26    match search {
27        Ok(index) => Some(index),
28        Err(index) => {
29            if index < cap {
30                Some(index)
31            } else {
32                None
33            }
34        }
35    }
36}
37
38/// Returns the index of the smallest element greater than or equal to the search
39/// key, or the last index.
40pub fn upper_bound_always(search: Result<usize, usize>, cap: usize) -> usize {
41    upper_bound(search, cap).unwrap_or(cap - 1)
42}
43
44/// Returns the index of the largest element less than or equal to the search
45/// key.
46///
47/// # Example
48/// ```
49/// use slice_search::*;
50///
51/// let array = [0, 1, 2, 3, 4, 6];
52///
53/// let search_result = BinarySearch::search(&array[..], &5);
54///
55/// assert_eq!(lower_bound(search_result), Some(4));
56/// ```
57#[inline(always)]
58pub fn lower_bound(search: Result<usize, usize>) -> Option<usize> {
59    match search {
60        Ok(index) => Some(index),
61        Err(0) => None,
62        Err(index) => Some(index - 1),
63    }
64}
65
66/// Returns the index of the biggest element less than or equal to the search
67/// key, or the first index.
68#[inline(always)]
69pub fn lower_bound_always(search: Result<usize, usize>) -> usize {
70    lower_bound(search).unwrap_or(0)
71}
72
73use core::borrow::Borrow;
74
75/// An algorithm for searching a sorted slice, e.g. Binary or Linear
76pub trait Search {
77    /// Search a slice of `T` by comparing with a given value of `T`
78    ///
79    /// If the value is found then `Result::Ok` is returned, containing the index of
80    /// the matching element. If there are multiple matches, then any one of the matches
81    /// could be returned. The index is chosen deterministically, but is subject to change
82    /// in future versions of Rust. If the value is not found then `Result::Err` is returned,
83    /// containing the index where a matching element could be inserted while maintaining sorted order.
84    ///
85    /// This method assumes that the given slice is sorted.
86    ///
87    /// # Example
88    ///
89    /// ```
90    /// use slice_search::*;
91    ///
92    /// let slice = [1, 2, 3, 5, 8];
93    /// let result = BinarySearch::search(&slice, &3);
94    /// assert_eq!(result, Ok(2));
95    ///
96    /// let result = BinarySearch::search(&slice, &6);
97    /// assert_eq!(result, Err(4));
98    /// ```
99    fn search<T: Ord>(slice: &[T], x: &T) -> Result<usize, usize> {
100        Self::search_by_key(slice, x)
101    }
102
103    /// Search a slice of `T`, where `T: Borrow<K>` and comparing to a given
104    /// value of `K`, using the `Borrow<K>` trait like a key extraction
105    /// function.
106    ///
107    /// If the value is found then `Result::Ok` is returned, containing the index of
108    /// the matching element. If there are multiple matches, then any one of the matches
109    /// could be returned. The index is chosen deterministically, but is subject to change
110    /// in future versions of Rust. If the value is not found then `Result::Err` is returned,
111    /// containing the index where a matching element could be inserted while maintaining sorted order.
112    ///
113    /// This method assumes that the given slice is sorted.
114    ///
115    /// ```
116    /// use slice_search::*;
117    ///
118    /// struct Object {
119    ///     key: i32,
120    /// }
121    ///
122    /// impl core::borrow::Borrow<i32> for Object {
123    ///     fn borrow(&self) -> &i32 {
124    ///         &self.key
125    ///     }
126    /// }
127    ///
128    /// let slice = [
129    ///     Object { key: 1 },
130    ///     Object { key: 3 },
131    ///     Object { key: 5 }
132    /// ];
133    ///
134    /// let result = BinarySearch::search_by_key(&slice, &3);
135    /// assert_eq!(result, Ok(1));
136    /// ```
137    fn search_by_key<K: Ord, T: Borrow<K>>(slice: &[T], x: &K) -> Result<usize, usize>;
138}
139
140/// Performs a binary search on a slice, with computational complexity `O(log n)`
141/// However, for small searches, a linear search may be faster.
142pub struct BinarySearch;
143
144impl Search for BinarySearch {
145    fn search_by_key<K: Ord, T: Borrow<K>>(slice: &[T], x: &K) -> Result<usize, usize> {
146        slice.binary_search_by(|y| y.borrow().cmp(x))
147    }
148}
149
150/// Performs a simple linear search on a slice, with computational complexity `O(n)`
151pub struct LinearSearch;
152
153impl Search for LinearSearch {
154    fn search_by_key<K: Ord, T: Borrow<K>>(slice: &[T], x: &K) -> Result<usize, usize> {
155        let mut index = 0;
156        let size = slice.len();
157
158        while index < size && unsafe { slice.get_unchecked(index).borrow() } < x {
159            index += 1;
160        }
161
162        if index >= size {
163            Err(size)
164        } else if unsafe { slice.get_unchecked(index).borrow() } == x {
165            Ok(index)
166        } else {
167            Err(index)
168        }
169    }
170}
171
172const BINARY_SEARCH_CUTOFF: usize = 1024;
173
174/// Chooses between binary and linear search depending on the size of the slice to search
175pub struct OptimalSearch;
176
177impl Search for OptimalSearch {
178    fn search_by_key<K: Ord, T: Borrow<K>>(slice: &[T], x: &K) -> Result<usize, usize> {
179        if slice.len() * core::mem::size_of::<K>() > BINARY_SEARCH_CUTOFF {
180            BinarySearch::search_by_key(slice, x)
181        } else {
182            LinearSearch::search_by_key(slice, x)
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_upper_bound() {
193        assert_eq!(upper_bound(Ok(3), 5), Some(3));
194        assert_eq!(upper_bound(Err(3), 5), Some(3));
195        assert_eq!(upper_bound(Err(5), 5), None);
196        assert_eq!(upper_bound(Err(7), 5), None);
197
198        assert_eq!(upper_bound_always(Ok(3), 5), 3);
199        assert_eq!(upper_bound_always(Err(3), 5), 3);
200        assert_eq!(upper_bound_always(Err(5), 5), 4);
201        assert_eq!(upper_bound_always(Err(7), 5), 4);
202    }
203
204    #[test]
205    fn test_lower_bound() {
206        assert_eq!(lower_bound(Ok(3)), Some(3));
207        assert_eq!(lower_bound(Err(3)), Some(2));
208        assert_eq!(lower_bound(Err(0)), None);
209
210        assert_eq!(lower_bound_always(Ok(3)), 3);
211        assert_eq!(lower_bound_always(Err(3)), 2);
212        assert_eq!(lower_bound_always(Err(0)), 0);
213    }
214
215    #[test]
216    fn binary_linear_search() {
217        let array = [1, 2, 3, 4, 7, 10, 24, 55, 56, 57, 100];
218        for i in -10..110 {
219            assert_eq!(
220                BinarySearch::search(&array[..], &i),
221                LinearSearch::search(&array[..], &i)
222            );
223        }
224    }
225
226    #[test]
227    fn binary_optimal_search() {
228        let array = [1, 2, 3, 4, 7, 10, 24, 55, 56, 57, 100];
229
230        for i in 0..1_000 {
231            assert_eq!(
232                BinarySearch::search(&array[..], &i),
233                OptimalSearch::search(&array[..], &i)
234            );
235        }
236    }
237}