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}