Skip to main content

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}