Skip to content

Commit 57355e9

Browse files
authored
binary-search: add approaches (#1630)
Add approaches for the `binary-search` exercise
1 parent e8c8982 commit 57355e9

File tree

6 files changed

+258
-0
lines changed

6 files changed

+258
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"introduction": {
3+
"authors": ["bobahop"],
4+
"contributors": []
5+
},
6+
"approaches": [
7+
{
8+
"uuid": "d440403a-cc86-4464-b230-b8bfef7206b6",
9+
"slug": "looping",
10+
"title": "Looping",
11+
"blurb": "Loop and match to return the answer.",
12+
"authors": ["bobahop"]
13+
},
14+
{
15+
"uuid": "8cd50244-9897-4ab9-a53e-463efe279812",
16+
"slug": "recursion",
17+
"title": "Recursion",
18+
"blurb": "Recurse and match to return the answer.",
19+
"authors": ["bobahop"]
20+
}
21+
]
22+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Introduction
2+
3+
There are at least two general ways to solve Binary Search.
4+
One approach is to loop.
5+
Another approach is to use recursion.
6+
7+
## General guidance
8+
9+
One thing to keep in mind is that Rust does not guarantee [tail call optimization][tco].
10+
11+
## Approach: Looping
12+
13+
```rust
14+
use std::cmp::Ordering;
15+
16+
pub fn find<U: AsRef<[T]>, T: Ord>(array: U, key: T) -> Option<usize> {
17+
let array = array.as_ref();
18+
let mut left = 0usize;
19+
let mut right = array.len();
20+
let mut mid: usize;
21+
22+
while left < right {
23+
mid = (left + right) / 2;
24+
match array[mid].cmp(&key) {
25+
Ordering::Equal => return Some(mid),
26+
Ordering::Less => {
27+
left = mid + 1;
28+
}
29+
Ordering::Greater => {
30+
right = mid;
31+
}
32+
}
33+
}
34+
None
35+
}
36+
```
37+
38+
For more information, check the [Looping approach][approach-looping].
39+
40+
## Approach: Recursion
41+
42+
```rust
43+
use std::cmp::Ordering;
44+
45+
fn find_rec<U: AsRef<[T]>, T: Ord>(array: U, key: T, offset: usize) -> Option<usize> {
46+
let array = array.as_ref();
47+
if array.len() == 0 {
48+
return None;
49+
}
50+
let mid = array.len() / 2;
51+
52+
match array[mid].cmp(&key) {
53+
Ordering::Equal => Some(offset + mid),
54+
Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1),
55+
Ordering::Greater => find_rec(&array[..mid], key, offset),
56+
}
57+
}
58+
59+
pub fn find<U: AsRef<[T]>, T: Ord>(array: U, key: T) -> Option<usize> {
60+
find_rec(array, key, 0)
61+
}
62+
```
63+
64+
For more information, check the [Recursion approach][approach-recursion].
65+
66+
## Which approach to use?
67+
68+
Since benchmarking is currently outside the scope of this document, which to use is pretty much a matter of personal preference,
69+
but the looping approach may be considered to be more idiomatic, given that Rust does not guarantee tail call optimization.
70+
71+
[tco]: https://stackoverflow.com/questions/59257543/when-is-tail-recursion-guaranteed-in-rust/59258170#59258170
72+
[approach-looping]: https://exercism.org/tracks/rust/exercises/binary-search/approaches/looping
73+
[approach-recursion]: https://exercism.org/tracks/rust/exercises/binary-search/approaches/recursion
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Looping
2+
3+
```rust
4+
use std::cmp::Ordering;
5+
6+
pub fn find<U: AsRef<[T]>, T: Ord>(array: U, key: T) -> Option<usize> {
7+
let array = array.as_ref();
8+
let mut left = 0usize;
9+
let mut right = array.len();
10+
let mut mid: usize;
11+
12+
while left < right {
13+
mid = (left + right) / 2;
14+
match array[mid].cmp(&key) {
15+
Ordering::Equal => return Some(mid),
16+
Ordering::Less => {
17+
left = mid + 1;
18+
}
19+
Ordering::Greater => {
20+
right = mid;
21+
}
22+
}
23+
}
24+
None
25+
}
26+
```
27+
28+
This approach starts by using the [`Ordering`][ordering-enum] enum.
29+
30+
The `find()` function has a signature to support the optional generic tests.
31+
To support slices, arrays and Vecs, which can be of varying lengths and sizes at runtime,
32+
the compiler needs to be given informaton it can know at compile time.
33+
A reference to any of those containers will always be of the same size (essentially the size of a pointer),
34+
so [`AsRef`][asref] is used to constrain the generic type to be anything that is a reference or can be coerced to a reference.
35+
36+
The `<[T]>` is used to constrain the reference type to an indexable type `T`.
37+
The `T` is constrained to be anything which implements the [`Ord`][ord] trait, which essentially means the values must be able to be ordered.
38+
39+
So, the `key` is of type `T` (orderable), and the `array` is of type `U` (a reference to an indexable container of orderable values
40+
of the same type as the `key`.)
41+
42+
Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`,
43+
the [`as_ref()`][asref] method is used to get the reference to the actual type.
44+
Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and
45+
"cannot index into a value of type `U`".
46+
47+
Next. some variables of type [`usize`][usize] are defined to keep track of where we are in the container.
48+
49+
The loop runs while `left` is less than `right`.
50+
If the `array` is empty, then `left` and `right` will both be `0`, and the loop won't run, and the function will return [`None`][none].
51+
52+
Inside the loop, the midpoint between `left` and `right` is used to get the element of the `array` at the index of the midpoint value.
53+
The [`cmp()`][cmp] method of the `Ord` trait is used to compare that element value with the key value.
54+
Since the element is a reference, the `key` must also be referenced.
55+
56+
The [`match`][match] arms each use a value from the `Ordering` enum.
57+
- If the midpoint element value equals the `key`, then the midpoint is returned from the function wrapped in a [`Some`][some].
58+
- If the midpoint element value is less than the `key`, then the `left` value is adjusted to be one to the right of the midpoint.
59+
- If the midpoint element value is greater than the `key`, then the `right` value is adjusted to be the midpoint.
60+
61+
While the element value is not equal to the `key`, the number of elements being searched keeps getting halved until
62+
either the `key` is found, or, if it is not in the `array`, the bounds meet and the loop no longer runs.
63+
64+
[ordering-enum]: https://doc.rust-lang.org/std/cmp/enum.Ordering.html
65+
[asref]: https://doc.rust-lang.org/std/convert/trait.AsRef.html
66+
[ord]: https://doc.rust-lang.org/std/cmp/trait.Ord.html
67+
[asref]: https://doc.rust-lang.org/std/convert/trait.AsRef.html#tymethod.as_ref
68+
[usize]: https://doc.rust-lang.org/std/primitive.usize.html
69+
[match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html
70+
[cmp]: https://doc.rust-lang.org/std/cmp/trait.Ord.html#tymethod.cmp
71+
[none]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None
72+
[some]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
let array = array.as_ref();
2+
let mut left = 0usize;
3+
let mut right = array.len();
4+
let mut mid: usize;
5+
6+
while left < right {
7+
mid = (left + right) / 2;
8+
match array[mid].cmp(&key) {
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Recursion
2+
3+
```rust
4+
use std::cmp::Ordering;
5+
6+
fn find_rec<U: AsRef<[T]>, T: Ord>(array: U, key: T, offset: usize) -> Option<usize> {
7+
let array = array.as_ref();
8+
if array.len() == 0 {
9+
return None;
10+
}
11+
let mid = array.len() / 2;
12+
13+
match array[mid].cmp(&key) {
14+
Ordering::Equal => Some(offset + mid),
15+
Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1),
16+
Ordering::Greater => find_rec(&array[..mid], key, offset),
17+
}
18+
}
19+
20+
pub fn find<U: AsRef<[T]>, T: Ord>(array: U, key: T) -> Option<usize> {
21+
find_rec(array, key, 0)
22+
}
23+
```
24+
25+
This approach starts by using the [`Ordering`][ordering-enum] enum.
26+
27+
The `find_rec()` function has a signature to support the optional generic tests.
28+
To support slices, arrays and Vecs, which can be of varying lengths and sizes at runtime,
29+
the compiler needs to be given informaton it can know at compile time.
30+
A reference to any of those containers will always be of the same size (essentially the size of a pointer),
31+
so [`AsRef`][asref] is used to constrain the generic type to be anything that is a reference or can be coerced to a reference.
32+
33+
The `<[T]>` is used to constrain the reference type to an indexable type `T`.
34+
The `T` is constrained to be anything which implements the [`Ord`][ord] trait, which essentially means the values must be able to be ordered.
35+
36+
So, the `key` is of type `T` (orderable), and the `array` is of type `U` (a reference to an indexable container of orderable values
37+
of the same type as the `key`.)
38+
39+
Since slices of the `array` will keep getting shorter with each recursive call to itself, `find_rec()` has an `offset` parameter
40+
to keep track of the actual midpoint as it relates to the original `array`.
41+
42+
Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`,
43+
the [`as_ref()`][asref] method is used to get the reference to the actual type.
44+
Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and
45+
"cannot index into a value of type `U`".
46+
47+
If the `array` is empty, then [`None`][none] is returned.
48+
49+
The midpoint of the `array` is used to get the element of the `array` at the index of the midpoint value.
50+
The [`cmp()`][cmp] method of the `Ord` trait is used to compare that element value with the key value.
51+
Since the element is a reference, the `key` must also be referenced.
52+
53+
The [`match`][match] arms each use a value from the `Ordering` enum.
54+
- If the midpoint element value equals the `key`, then the midpoint plus the offset is returned from the function wrapped in a [`Some`][some].
55+
- If the midpoint element value is less than the `key`, then `find_rec()` calls itself,
56+
passing a slice of the `array` from the element to the right of the midpoint through the end of the `array`.
57+
The offset is adjusted to be itself plus the midpoint plus `1`.
58+
- If the midpoint element value is greater than the `key`, then `find_rec()` calls itself,
59+
passing a slice of the `array` from the beginning up to but not including the midpoint element.
60+
The offset remains as is.
61+
62+
While the element value is not equal to the `key`, `find_rec()` keeps calling itself while halving the number of elements being searched,
63+
until either the `key` is found, or, if it is not in the `array`, the `array` is whittled down to empty.
64+
65+
The `find()` method returns the final result from calling the `find_rec()` method, passing in the `array`, `key`, and `0` for the initial
66+
offset value.
67+
68+
[ordering-enum]: https://doc.rust-lang.org/std/cmp/enum.Ordering.html
69+
[asref]: https://doc.rust-lang.org/std/convert/trait.AsRef.html
70+
[ord]: https://doc.rust-lang.org/std/cmp/trait.Ord.html
71+
[asref]: https://doc.rust-lang.org/std/convert/trait.AsRef.html#tymethod.as_ref
72+
[usize]: https://doc.rust-lang.org/std/primitive.usize.html
73+
[match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html
74+
[cmp]: https://doc.rust-lang.org/std/cmp/trait.Ord.html#tymethod.cmp
75+
[none]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None
76+
[some]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let mid = array.len() / 2;
2+
3+
match array[mid].cmp(&key) {
4+
Ordering::Equal => Some(offset + mid),
5+
Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1),
6+
Ordering::Greater => find_rec(&array[..mid], key, offset),
7+
}

0 commit comments

Comments
 (0)