search_sort/search.rs
1//! Implementations of searching algorithms.
2
3use std::cmp::Ordering;
4
5/// An implementation of linear search.
6///
7/// Looks for the value in the slice by iterating over it. Returns the position
8/// of its first equal element, or [`None`] if not found.
9///
10/// `slice.iter().find(|&x| x == &value)` does almost the same thing.
11///
12/// # Examples
13///
14/// ```
15/// use search_sort::search;
16///
17/// let slice = [1, 85, 23, -4, 8];
18/// assert_eq!(search::linear(&slice, &23), Some(2));
19/// assert_eq!(search::linear(&slice, &-77), None);
20/// ```
21pub fn linear<T: PartialEq>(slice: &[T], value: &T) -> Option<usize> {
22 for (i, v) in slice.iter().enumerate() {
23 if value == v {
24 return Some(i);
25 }
26 }
27
28 None
29}
30
31/// An implementation of binary search.
32///
33/// Recursively searches for the value in a sorted slice. It does the following:
34/// * computes the center of the slice (size / 2),
35/// * compares it with the value,
36/// * if it's smaller, invokes itself with the first part of the slice,
37/// * if they are equal, returns the center,
38/// * if it's greater, invokes itself with the second part of the slice and
39/// adds the current center and 1.
40/// * if didn't find the value (center == 0 || center >= size - 1), returns
41/// [`None`].
42///
43/// **Note**: the returned value is the position of the first found element,
44/// that may not be the position of the first element in the whole slice. Use
45/// [`binary_first`] instead.
46///
47/// # Examples
48///
49/// ```
50/// use search_sort::search;
51///
52/// let slice = [1, 2, 4, 8, 16, 32];
53/// assert_eq!(search::binary(&slice, &8), Some(3));
54/// assert_eq!(search::binary(&slice, &3), None);
55/// ```
56pub fn binary<T: Ord>(slice: &[T], value: &T) -> Option<usize> {
57 let mid = slice.len() / 2;
58 match value.cmp(&slice[mid]) {
59 Ordering::Less if mid > 0 => binary(&slice[0..mid], value),
60 Ordering::Equal => Some(mid),
61 Ordering::Greater if mid < slice.len() - 1 => {
62 binary(&slice[(mid + 1)..slice.len()], value).map(|x| x + mid + 1)
63 }
64 _ => None,
65 }
66}
67
68/// An implementation of binary search that finds the very first position of
69/// the element.
70///
71/// Invokes [`binary`] search and iterates over the elements backward in
72/// the slice before the found element. Returns the position of the last
73/// (first in the slice) equal element.
74///
75/// # Examples
76///
77/// ```
78/// use search_sort::search;
79///
80/// let fib = [1, 1, 2, 3];
81/// // the first found element is on position 1, since it doesn't check the
82/// // elements before
83/// assert_eq!(search::binary(&fib, &1), Some(1));
84/// assert_eq!(search::binary_first(&fib, &1), Some(0));
85/// ```
86pub fn binary_first<T: Ord>(slice: &[T], value: &T) -> Option<usize> {
87 let pos = binary(slice, value);
88 match pos {
89 Some(pos) => {
90 for (i, v) in slice[0..pos].iter().enumerate().rev() {
91 if v < value {
92 return Some(i + 1);
93 }
94 }
95 Some(0)
96 }
97 None => None,
98 }
99}
100
101/// An implementation of jump search with custom `step`.
102///
103/// Jumps over a sorted slice by fixed steps, until it finds the largest
104/// element, smaller than the value. Then invokes [linear] search from this
105/// element to the next step.
106///
107/// It's usually slower than `binary` search, except when the value is expected
108/// to be on the beggining of the slice.
109///
110/// See also [`jump`] function.
111pub fn jump_step<T: Ord>(slice: &[T], value: &T, step: usize) -> Option<usize> {
112 if step == 1 {
113 return linear(slice, value);
114 } else if step == 0 {
115 // it would be stuck on the first element
116 if &slice[0] == value {
117 return Some(0);
118 } else {
119 return None;
120 }
121 }
122
123 let mut iter = slice.iter();
124 let mut pos: usize = 0;
125
126 // if found larger than the value
127 let mut found = false;
128
129 for i in 0..(slice.len() / step) {
130 match value.cmp(iter.next().unwrap()) {
131 Ordering::Less => {
132 if i == 0 {
133 // smaller than every element
134 return None;
135 }
136
137 found = true;
138 break;
139 }
140 Ordering::Equal => return Some(i * step),
141 Ordering::Greater => {
142 pos = i * step;
143 if i >= slice.len() {
144 // larger than every element
145 return None;
146 }
147 }
148 }
149
150 // Iterator::nth(...) adds 1 automatically; hence step - 1
151 // additionally, Iterator::next() is invoked above; thus step - 2
152 iter.nth(step - 2).unwrap();
153 }
154
155 let mut end = pos + step;
156 if !found {
157 // if did not found, then check the rest of the slice, since the loop
158 // may not have done it; plus pos + step could be higher than len - 1
159 end = slice.len()
160 }
161
162 // no need to check the element on pos
163 linear(&slice[(pos + 1)..end], value).map(|x| x + pos + 1)
164}
165
166/// An implementation of jump search with optimal `step`.
167///
168/// Invokes [`jump_step`] search with square root of the length of the slice.
169/// It does `(len / step) + step - 1` comparisions at most; there would be the
170/// least of them if `step = sqrt(len)`.
171///
172/// # Examples
173///
174/// ```
175/// use search_sort::search;
176///
177/// let slice = [1, 5, 7, 15, 31, 32, 45];
178/// assert_eq!(search::jump(&slice, &15), Some(3));
179/// ```
180pub fn jump<T: Ord>(slice: &[T], value: &T) -> Option<usize> {
181 jump_step(slice, value, (slice.len() as f64).sqrt() as usize)
182}
183
184/// An implementation of exponential search.
185///
186/// Finds a range where the element may be found, and calls [`binary_first`] on
187/// it. This range is between a power of 2 and its next power.
188///
189/// # Examples
190///
191/// ```
192/// use search_sort::search;
193///
194/// let slice = [2, 4, 6, 7, 11, 12, 17];
195/// assert_eq!(search::exp(&slice, &6), Some(2));
196/// ```
197pub fn exp<T: Ord>(slice: &[T], value: &T) -> Option<usize> {
198 if &slice[0] == value {
199 // the loop doesn't check the first element
200 return Some(0);
201 }
202
203 let mut exp = 0;
204 let start = loop {
205 let i = usize::pow(2, exp);
206 match value.cmp(&slice[i]) {
207 Ordering::Less if i > 1 => break i / 2,
208 Ordering::Equal => return Some(i),
209 Ordering::Greater if i < slice.len() - 1 => {}
210 _ => return None,
211 }
212
213 exp += 1;
214 };
215
216 binary_first(&slice[start..(start * 2)], value).map(|x| x + start)
217}
218
219#[cfg(test)]
220mod tests {
221 use super::binary;
222 use super::binary_first;
223 use super::jump;
224 use super::linear;
225
226 #[test]
227 fn linear_test() {
228 assert_eq!(linear(&[0, 5, -7, 100, 67, -23], &-7), Some(2));
229 assert_eq!(linear(&[11, -25, 12, 85, -8], &6), None)
230 }
231
232 #[test]
233 fn binary_test() {
234 let fib = [1, 1, 2, 3, 5, 8, 13, 21];
235 assert_eq!(binary(&fib, &5), Some(4));
236 assert_eq!(binary(&fib, &21), Some(7));
237
238 let primes = [1, 2, 3, 5, 7, 11, 13, 17];
239 assert_eq!(binary(&primes, &8), None);
240 assert_eq!(binary(&primes, &0), None);
241 assert_eq!(binary(&primes, &18), None);
242 }
243
244 #[test]
245 fn binary_first_test() {
246 assert_eq!(binary(&[1, 1, 2, 3], &1), Some(1));
247 assert_eq!(binary_first(&[1, 1, 2, 3], &1), Some(0));
248 }
249
250 #[test]
251 fn jump_test() {
252 assert_eq!(jump(&[2, 5, 6, 11], &5), Some(1));
253 assert_eq!(jump(&[2, 5, 6, 11], &4), None);
254 }
255
256 #[test]
257 fn exp_test() {
258 let slice = [-2, 0, 3, 6, 7, 12, 23, 25, 31, 41];
259 assert_eq!(jump(&slice, &12), Some(5));
260 assert_eq!(jump(&slice, &13), None);
261 }
262}