|
1 | 1 | use std::cmp::Ordering; |
2 | 2 |
|
3 | | -pub fn binary_search_rec<T: Ord>( |
4 | | - list_of_items: &[T], |
5 | | - target: &T, |
6 | | - left: &usize, |
7 | | - right: &usize, |
8 | | -) -> Option<usize> { |
| 3 | +/// Recursively performs a binary search for a specified item within a sorted array. |
| 4 | +/// |
| 5 | +/// This function can handle both ascending and descending ordered arrays. It |
| 6 | +/// takes a reference to the item to search for and a slice of the array. If |
| 7 | +/// the item is found, it returns the index of the item within the array. If |
| 8 | +/// the item is not found, it returns `None`. |
| 9 | +/// |
| 10 | +/// # Parameters |
| 11 | +/// |
| 12 | +/// - `item`: A reference to the item to search for. |
| 13 | +/// - `arr`: A slice of the sorted array in which to search. |
| 14 | +/// - `left`: The left bound of the current search range. |
| 15 | +/// - `right`: The right bound of the current search range. |
| 16 | +/// - `is_asc`: A boolean indicating whether the array is sorted in ascending order. |
| 17 | +/// |
| 18 | +/// # Returns |
| 19 | +/// |
| 20 | +/// An `Option<usize>` which is: |
| 21 | +/// - `Some(index)` if the item is found at the given index. |
| 22 | +/// - `None` if the item is not found in the array. |
| 23 | +pub fn binary_search_rec<T: Ord>(item: &T, arr: &[T], left: usize, right: usize) -> Option<usize> { |
9 | 24 | if left >= right { |
10 | 25 | return None; |
11 | 26 | } |
12 | 27 |
|
13 | | - let is_asc = list_of_items[0] < list_of_items[list_of_items.len() - 1]; |
| 28 | + let is_asc = arr.len() > 1 && arr[0] < arr[arr.len() - 1]; |
| 29 | + let mid = left + (right - left) / 2; |
| 30 | + let cmp_result = item.cmp(&arr[mid]); |
14 | 31 |
|
15 | | - let middle: usize = left + (right - left) / 2; |
16 | | - |
17 | | - if is_asc { |
18 | | - match target.cmp(&list_of_items[middle]) { |
19 | | - Ordering::Less => binary_search_rec(list_of_items, target, left, &middle), |
20 | | - Ordering::Greater => binary_search_rec(list_of_items, target, &(middle + 1), right), |
21 | | - Ordering::Equal => Some(middle), |
| 32 | + match (is_asc, cmp_result) { |
| 33 | + (true, Ordering::Less) | (false, Ordering::Greater) => { |
| 34 | + binary_search_rec(item, arr, left, mid) |
22 | 35 | } |
23 | | - } else { |
24 | | - match target.cmp(&list_of_items[middle]) { |
25 | | - Ordering::Less => binary_search_rec(list_of_items, target, &(middle + 1), right), |
26 | | - Ordering::Greater => binary_search_rec(list_of_items, target, left, &middle), |
27 | | - Ordering::Equal => Some(middle), |
| 36 | + (true, Ordering::Greater) | (false, Ordering::Less) => { |
| 37 | + binary_search_rec(item, arr, mid + 1, right) |
28 | 38 | } |
| 39 | + (_, Ordering::Equal) => Some(mid), |
29 | 40 | } |
30 | 41 | } |
31 | 42 |
|
32 | 43 | #[cfg(test)] |
33 | 44 | mod tests { |
34 | 45 | use super::*; |
35 | 46 |
|
36 | | - const LEFT: usize = 0; |
37 | | - |
38 | | - #[test] |
39 | | - fn fail_empty_list() { |
40 | | - let list_of_items = vec![]; |
41 | | - assert_eq!( |
42 | | - binary_search_rec(&list_of_items, &1, &LEFT, &list_of_items.len()), |
43 | | - None |
44 | | - ); |
45 | | - } |
46 | | - |
47 | | - #[test] |
48 | | - fn success_one_item() { |
49 | | - let list_of_items = vec![30]; |
50 | | - assert_eq!( |
51 | | - binary_search_rec(&list_of_items, &30, &LEFT, &list_of_items.len()), |
52 | | - Some(0) |
53 | | - ); |
54 | | - } |
55 | | - |
56 | | - #[test] |
57 | | - fn success_search_strings_asc() { |
58 | | - let say_hello_list = vec!["hi", "olá", "salut"]; |
59 | | - let right = say_hello_list.len(); |
60 | | - assert_eq!( |
61 | | - binary_search_rec(&say_hello_list, &"hi", &LEFT, &right), |
62 | | - Some(0) |
63 | | - ); |
64 | | - assert_eq!( |
65 | | - binary_search_rec(&say_hello_list, &"salut", &LEFT, &right), |
66 | | - Some(2) |
67 | | - ); |
68 | | - } |
69 | | - |
70 | | - #[test] |
71 | | - fn success_search_strings_desc() { |
72 | | - let say_hello_list = vec!["salut", "olá", "hi"]; |
73 | | - let right = say_hello_list.len(); |
74 | | - assert_eq!( |
75 | | - binary_search_rec(&say_hello_list, &"hi", &LEFT, &right), |
76 | | - Some(2) |
77 | | - ); |
78 | | - assert_eq!( |
79 | | - binary_search_rec(&say_hello_list, &"salut", &LEFT, &right), |
80 | | - Some(0) |
81 | | - ); |
82 | | - } |
83 | | - |
84 | | - #[test] |
85 | | - fn fail_search_strings_asc() { |
86 | | - let say_hello_list = vec!["hi", "olá", "salut"]; |
87 | | - for target in &["adiós", "你好"] { |
88 | | - assert_eq!( |
89 | | - binary_search_rec(&say_hello_list, target, &LEFT, &say_hello_list.len()), |
90 | | - None |
91 | | - ); |
92 | | - } |
93 | | - } |
94 | | - |
95 | | - #[test] |
96 | | - fn fail_search_strings_desc() { |
97 | | - let say_hello_list = vec!["salut", "olá", "hi"]; |
98 | | - for target in &["adiós", "你好"] { |
99 | | - assert_eq!( |
100 | | - binary_search_rec(&say_hello_list, target, &LEFT, &say_hello_list.len()), |
101 | | - None |
102 | | - ); |
103 | | - } |
104 | | - } |
105 | | - |
106 | | - #[test] |
107 | | - fn success_search_integers_asc() { |
108 | | - let integers = vec![0, 10, 20, 30, 40, 50, 60, 70, 80, 90]; |
109 | | - for (index, target) in integers.iter().enumerate() { |
110 | | - assert_eq!( |
111 | | - binary_search_rec(&integers, target, &LEFT, &integers.len()), |
112 | | - Some(index) |
113 | | - ) |
114 | | - } |
115 | | - } |
116 | | - |
117 | | - #[test] |
118 | | - fn success_search_integers_desc() { |
119 | | - let integers = vec![90, 80, 70, 60, 50, 40, 30, 20, 10, 0]; |
120 | | - for (index, target) in integers.iter().enumerate() { |
121 | | - assert_eq!( |
122 | | - binary_search_rec(&integers, target, &LEFT, &integers.len()), |
123 | | - Some(index) |
124 | | - ) |
125 | | - } |
126 | | - } |
127 | | - |
128 | | - #[test] |
129 | | - fn fail_search_integers() { |
130 | | - let integers = vec![0, 10, 20, 30, 40, 50, 60, 70, 80, 90]; |
131 | | - for target in &[100, 444, 336] { |
132 | | - assert_eq!( |
133 | | - binary_search_rec(&integers, target, &LEFT, &integers.len()), |
134 | | - None |
135 | | - ); |
136 | | - } |
137 | | - } |
138 | | - |
139 | | - #[test] |
140 | | - fn success_search_string_in_middle_of_unsorted_list() { |
141 | | - let unsorted_strings = vec!["salut", "olá", "hi"]; |
142 | | - assert_eq!( |
143 | | - binary_search_rec(&unsorted_strings, &"olá", &LEFT, &unsorted_strings.len()), |
144 | | - Some(1) |
145 | | - ); |
| 47 | + macro_rules! test_cases { |
| 48 | + ($($name:ident: $test_case:expr,)*) => { |
| 49 | + $( |
| 50 | + #[test] |
| 51 | + fn $name() { |
| 52 | + let (item, arr, expected) = $test_case; |
| 53 | + assert_eq!(binary_search_rec(&item, arr, 0, arr.len()), expected); |
| 54 | + } |
| 55 | + )* |
| 56 | + }; |
146 | 57 | } |
147 | 58 |
|
148 | | - #[test] |
149 | | - fn success_search_integer_in_middle_of_unsorted_list() { |
150 | | - let unsorted_integers = vec![90, 80, 70]; |
151 | | - assert_eq!( |
152 | | - binary_search_rec(&unsorted_integers, &80, &LEFT, &unsorted_integers.len()), |
153 | | - Some(1) |
154 | | - ); |
| 59 | + test_cases! { |
| 60 | + empty: ("a", &[] as &[&str], None), |
| 61 | + one_item_found: ("a", &["a"], Some(0)), |
| 62 | + one_item_not_found: ("b", &["a"], None), |
| 63 | + search_strings_asc_start: ("a", &["a", "b", "c", "d", "google", "zoo"], Some(0)), |
| 64 | + search_strings_asc_middle: ("google", &["a", "b", "c", "d", "google", "zoo"], Some(4)), |
| 65 | + search_strings_asc_last: ("zoo", &["a", "b", "c", "d", "google", "zoo"], Some(5)), |
| 66 | + search_strings_asc_not_found: ("x", &["a", "b", "c", "d", "google", "zoo"], None), |
| 67 | + search_strings_desc_start: ("zoo", &["zoo", "google", "d", "c", "b", "a"], Some(0)), |
| 68 | + search_strings_desc_middle: ("google", &["zoo", "google", "d", "c", "b", "a"], Some(1)), |
| 69 | + search_strings_desc_last: ("a", &["zoo", "google", "d", "c", "b", "a"], Some(5)), |
| 70 | + search_strings_desc_not_found: ("x", &["zoo", "google", "d", "c", "b", "a"], None), |
| 71 | + search_ints_asc_start: (1, &[1, 2, 3, 4], Some(0)), |
| 72 | + search_ints_asc_middle: (3, &[1, 2, 3, 4], Some(2)), |
| 73 | + search_ints_asc_end: (4, &[1, 2, 3, 4], Some(3)), |
| 74 | + search_ints_asc_not_found: (5, &[1, 2, 3, 4], None), |
| 75 | + search_ints_desc_start: (4, &[4, 3, 2, 1], Some(0)), |
| 76 | + search_ints_desc_middle: (3, &[4, 3, 2, 1], Some(1)), |
| 77 | + search_ints_desc_end: (1, &[4, 3, 2, 1], Some(3)), |
| 78 | + search_ints_desc_not_found: (5, &[4, 3, 2, 1], None), |
| 79 | + with_gaps_0: (0, &[1, 3, 8, 11], None), |
| 80 | + with_gaps_1: (1, &[1, 3, 8, 11], Some(0)), |
| 81 | + with_gaps_2: (2, &[1, 3, 8, 11], None), |
| 82 | + with_gaps_3: (3, &[1, 3, 8, 11], Some(1)), |
| 83 | + with_gaps_4: (4, &[1, 3, 8, 10], None), |
| 84 | + with_gaps_5: (5, &[1, 3, 8, 10], None), |
| 85 | + with_gaps_6: (6, &[1, 3, 8, 10], None), |
| 86 | + with_gaps_7: (7, &[1, 3, 8, 11], None), |
| 87 | + with_gaps_8: (8, &[1, 3, 8, 11], Some(2)), |
| 88 | + with_gaps_9: (9, &[1, 3, 8, 11], None), |
| 89 | + with_gaps_10: (10, &[1, 3, 8, 11], None), |
| 90 | + with_gaps_11: (11, &[1, 3, 8, 11], Some(3)), |
| 91 | + with_gaps_12: (12, &[1, 3, 8, 11], None), |
| 92 | + with_gaps_13: (13, &[1, 3, 8, 11], None), |
155 | 93 | } |
156 | 94 | } |
0 commit comments