Skip to content

Commit 4e59645

Browse files
sozelfistvil02
andauthored
Refactor Binary Search (#722)
* ref: refactor binary search - Simplyfy implementation logic - Add docstring - Rewrite tests using macro * ref: eliminate nested match statements * ref(tests): add suggested tests --------- Co-authored-by: Piotr Idzik <[email protected]>
1 parent c2009cd commit 4e59645

File tree

1 file changed

+132
-85
lines changed

1 file changed

+132
-85
lines changed

src/searching/binary_search.rs

Lines changed: 132 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,153 @@
1+
//! This module provides an implementation of a binary search algorithm that
2+
//! works for both ascending and descending ordered arrays. The binary search
3+
//! function returns the index of the target element if it is found, or `None`
4+
//! if the target is not present in the array.
5+
16
use std::cmp::Ordering;
27

8+
/// Performs a binary search for a specified item within a sorted array.
9+
///
10+
/// This function can handle both ascending and descending ordered arrays. It
11+
/// takes a reference to the item to search for and a slice of the array. If
12+
/// the item is found, it returns the index of the item within the array. If
13+
/// the item is not found, it returns `None`.
14+
///
15+
/// # Parameters
16+
///
17+
/// - `item`: A reference to the item to search for.
18+
/// - `arr`: A slice of the sorted array in which to search.
19+
///
20+
/// # Returns
21+
///
22+
/// An `Option<usize>` which is:
23+
/// - `Some(index)` if the item is found at the given index.
24+
/// - `None` if the item is not found in the array.
325
pub fn binary_search<T: Ord>(item: &T, arr: &[T]) -> Option<usize> {
4-
let mut is_asc = true;
5-
if arr.len() > 1 {
6-
is_asc = arr[0] < arr[arr.len() - 1];
7-
}
26+
let is_asc = is_asc_arr(arr);
27+
828
let mut left = 0;
929
let mut right = arr.len();
1030

1131
while left < right {
12-
let mid = left + (right - left) / 2;
13-
14-
if is_asc {
15-
match item.cmp(&arr[mid]) {
16-
Ordering::Less => right = mid,
17-
Ordering::Equal => return Some(mid),
18-
Ordering::Greater => left = mid + 1,
19-
}
20-
} else {
21-
match item.cmp(&arr[mid]) {
22-
Ordering::Less => left = mid + 1,
23-
Ordering::Equal => return Some(mid),
24-
Ordering::Greater => right = mid,
25-
}
32+
if match_compare(item, arr, &mut left, &mut right, is_asc) {
33+
return Some(left);
2634
}
2735
}
36+
2837
None
2938
}
3039

31-
#[cfg(test)]
32-
mod tests {
33-
use super::*;
34-
35-
#[test]
36-
fn empty() {
37-
let index = binary_search(&"a", &[]);
38-
assert_eq!(index, None);
39-
}
40-
41-
#[test]
42-
fn one_item() {
43-
let index = binary_search(&"a", &["a"]);
44-
assert_eq!(index, Some(0));
45-
}
46-
47-
#[test]
48-
fn search_strings_asc() {
49-
let index = binary_search(&"a", &["a", "b", "c", "d", "google", "zoo"]);
50-
assert_eq!(index, Some(0));
51-
52-
let index = binary_search(&"google", &["a", "b", "c", "d", "google", "zoo"]);
53-
assert_eq!(index, Some(4));
54-
}
55-
56-
#[test]
57-
fn search_strings_desc() {
58-
let index = binary_search(&"a", &["zoo", "google", "d", "c", "b", "a"]);
59-
assert_eq!(index, Some(5));
60-
61-
let index = binary_search(&"zoo", &["zoo", "google", "d", "c", "b", "a"]);
62-
assert_eq!(index, Some(0));
63-
64-
let index = binary_search(&"google", &["zoo", "google", "d", "c", "b", "a"]);
65-
assert_eq!(index, Some(1));
66-
}
67-
68-
#[test]
69-
fn search_ints_asc() {
70-
let index = binary_search(&4, &[1, 2, 3, 4]);
71-
assert_eq!(index, Some(3));
72-
73-
let index = binary_search(&3, &[1, 2, 3, 4]);
74-
assert_eq!(index, Some(2));
75-
76-
let index = binary_search(&2, &[1, 2, 3, 4]);
77-
assert_eq!(index, Some(1));
78-
79-
let index = binary_search(&1, &[1, 2, 3, 4]);
80-
assert_eq!(index, Some(0));
40+
/// Compares the item with the middle element of the current search range and
41+
/// updates the search bounds accordingly. This function handles both ascending
42+
/// and descending ordered arrays. It calculates the middle index of the
43+
/// current search range and compares the item with the element at
44+
/// this index. It then updates the search bounds (`left` and `right`) based on
45+
/// the result of this comparison. If the item is found, it updates `left` to
46+
/// the index of the found item and returns `true`.
47+
///
48+
/// # Parameters
49+
///
50+
/// - `item`: A reference to the item to search for.
51+
/// - `arr`: A slice of the array in which to search.
52+
/// - `left`: A mutable reference to the left bound of the search range.
53+
/// - `right`: A mutable reference to the right bound of the search range.
54+
/// - `is_asc`: A boolean indicating whether the array is sorted in ascending order.
55+
///
56+
/// # Returns
57+
///
58+
/// A `bool` indicating whether the item was found.
59+
fn match_compare<T: Ord>(
60+
item: &T,
61+
arr: &[T],
62+
left: &mut usize,
63+
right: &mut usize,
64+
is_asc: bool,
65+
) -> bool {
66+
let mid = *left + (*right - *left) / 2;
67+
let cmp_result = item.cmp(&arr[mid]);
68+
69+
match (is_asc, cmp_result) {
70+
(true, Ordering::Less) | (false, Ordering::Greater) => {
71+
*right = mid;
72+
}
73+
(true, Ordering::Greater) | (false, Ordering::Less) => {
74+
*left = mid + 1;
75+
}
76+
(_, Ordering::Equal) => {
77+
*left = mid;
78+
return true;
79+
}
8180
}
8281

83-
#[test]
84-
fn search_ints_desc() {
85-
let index = binary_search(&4, &[4, 3, 2, 1]);
86-
assert_eq!(index, Some(0));
82+
false
83+
}
8784

88-
let index = binary_search(&3, &[4, 3, 2, 1]);
89-
assert_eq!(index, Some(1));
85+
/// Determines if the given array is sorted in ascending order.
86+
///
87+
/// This helper function checks if the first element of the array is less than the
88+
/// last element, indicating an ascending order. It returns `false` if the array
89+
/// has fewer than two elements.
90+
///
91+
/// # Parameters
92+
///
93+
/// - `arr`: A slice of the array to check.
94+
///
95+
/// # Returns
96+
///
97+
/// A `bool` indicating whether the array is sorted in ascending order.
98+
fn is_asc_arr<T: Ord>(arr: &[T]) -> bool {
99+
arr.len() > 1 && arr[0] < arr[arr.len() - 1]
100+
}
90101

91-
let index = binary_search(&2, &[4, 3, 2, 1]);
92-
assert_eq!(index, Some(2));
102+
#[cfg(test)]
103+
mod tests {
104+
use super::*;
93105

94-
let index = binary_search(&1, &[4, 3, 2, 1]);
95-
assert_eq!(index, Some(3));
106+
macro_rules! test_cases {
107+
($($name:ident: $test_case:expr,)*) => {
108+
$(
109+
#[test]
110+
fn $name() {
111+
let (item, arr, expected) = $test_case;
112+
assert_eq!(binary_search(&item, arr), expected);
113+
}
114+
)*
115+
};
96116
}
97117

98-
#[test]
99-
fn not_found() {
100-
let index = binary_search(&5, &[1, 2, 3, 4]);
101-
assert_eq!(index, None);
102-
103-
let index = binary_search(&5, &[4, 3, 2, 1]);
104-
assert_eq!(index, None);
118+
test_cases! {
119+
empty: ("a", &[] as &[&str], None),
120+
one_item_found: ("a", &["a"], Some(0)),
121+
one_item_not_found: ("b", &["a"], None),
122+
search_strings_asc_start: ("a", &["a", "b", "c", "d", "google", "zoo"], Some(0)),
123+
search_strings_asc_middle: ("google", &["a", "b", "c", "d", "google", "zoo"], Some(4)),
124+
search_strings_asc_last: ("zoo", &["a", "b", "c", "d", "google", "zoo"], Some(5)),
125+
search_strings_asc_not_found: ("x", &["a", "b", "c", "d", "google", "zoo"], None),
126+
search_strings_desc_start: ("zoo", &["zoo", "google", "d", "c", "b", "a"], Some(0)),
127+
search_strings_desc_middle: ("google", &["zoo", "google", "d", "c", "b", "a"], Some(1)),
128+
search_strings_desc_last: ("a", &["zoo", "google", "d", "c", "b", "a"], Some(5)),
129+
search_strings_desc_not_found: ("x", &["zoo", "google", "d", "c", "b", "a"], None),
130+
search_ints_asc_start: (1, &[1, 2, 3, 4], Some(0)),
131+
search_ints_asc_middle: (3, &[1, 2, 3, 4], Some(2)),
132+
search_ints_asc_end: (4, &[1, 2, 3, 4], Some(3)),
133+
search_ints_asc_not_found: (5, &[1, 2, 3, 4], None),
134+
search_ints_desc_start: (4, &[4, 3, 2, 1], Some(0)),
135+
search_ints_desc_middle: (3, &[4, 3, 2, 1], Some(1)),
136+
search_ints_desc_end: (1, &[4, 3, 2, 1], Some(3)),
137+
search_ints_desc_not_found: (5, &[4, 3, 2, 1], None),
138+
with_gaps_0: (0, &[1, 3, 8, 11], None),
139+
with_gaps_1: (1, &[1, 3, 8, 11], Some(0)),
140+
with_gaps_2: (2, &[1, 3, 8, 11], None),
141+
with_gaps_3: (3, &[1, 3, 8, 11], Some(1)),
142+
with_gaps_4: (4, &[1, 3, 8, 10], None),
143+
with_gaps_5: (5, &[1, 3, 8, 10], None),
144+
with_gaps_6: (6, &[1, 3, 8, 10], None),
145+
with_gaps_7: (7, &[1, 3, 8, 11], None),
146+
with_gaps_8: (8, &[1, 3, 8, 11], Some(2)),
147+
with_gaps_9: (9, &[1, 3, 8, 11], None),
148+
with_gaps_10: (10, &[1, 3, 8, 11], None),
149+
with_gaps_11: (11, &[1, 3, 8, 11], Some(3)),
150+
with_gaps_12: (12, &[1, 3, 8, 11], None),
151+
with_gaps_13: (13, &[1, 3, 8, 11], None),
105152
}
106153
}

0 commit comments

Comments
 (0)