Skip to content

Commit 4ab48f7

Browse files
authored
sieve: add approaches (#1631)
Add approaches for the `sieve` exercise
1 parent 57355e9 commit 4ab48f7

File tree

6 files changed

+226
-0
lines changed

6 files changed

+226
-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": "32faf3d8-5035-4242-992b-be92a248184b",
9+
"slug": "ranges-and-filtermap",
10+
"title": "ranges and filter_map",
11+
"blurb": "Use two ranges and filter_map to iterate to the result.",
12+
"authors": ["bobahop"]
13+
},
14+
{
15+
"uuid": "d3deae2e-fa6c-42d9-9e94-6af5de090090",
16+
"slug": "for-in-ranges-with-filter",
17+
"title": "for in ranges with filter",
18+
"blurb": "Use two for in ranges with filter to iterate to the result.",
19+
"authors": ["bobahop"]
20+
}
21+
]
22+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# `for` in ranges with `filter()`
2+
3+
```rust
4+
pub fn primes_up_to(upper_bound: u64) -> Vec<u64> {
5+
let mut work: Vec<u64> = (0..=upper_bound).collect();
6+
work[1] = 0;
7+
let stop = (upper_bound as f64).sqrt() as usize + 1usize;
8+
let upper_bound = upper_bound as usize;
9+
for i in 2..stop {
10+
if (work[i]) != 0 {
11+
for idx in (i * i..=upper_bound).step_by(i) {
12+
work[idx] = 0;
13+
}
14+
}
15+
}
16+
work.iter().filter(|num| *num != &0u64).copied().collect()
17+
}
18+
```
19+
20+
This approach starts by defining a `Vec` of values from `0` through the upper bound.
21+
Since numbers below `2` are not valid primes, the element at index `1` is set to `0`.
22+
23+
Since squares of numbers are processed, the [`sqrt()`][sqrt] method is used to determine when iterating the `Vec` indexes will stop.
24+
25+
To minimize type conversions, the upper bound is redefined as a [`usize`][usize].
26+
27+
A [`for` in range][for-in-range] is defined from `2` up to but not including the `stop` value.
28+
Each number in the range is used inside the loop as an index to test if the element value in the `Vec` at that index is not `0`.
29+
30+
If the element value is not `0`, then an inner `for` in range is defined, starting with the number times itself and going through the upper bound.
31+
The [`step_by()`][stepby] method is used to traverse the range in steps the size of the outer range number.
32+
33+
Each number from the inner range is used as an index to set the element value at that index in the `Vec` to `0`.
34+
35+
When the outer range is done, the `Vec` is passed through the [`iter()`][iter] method to the [`filter()`][filter] method.
36+
The [closure][closure] (also known as a lambda) in the body of the `filter()` tests that the element is not `0`.
37+
It dereferences the number to convert it from a "reference to a reference" to a "reference", and it references the `0` literal
38+
`u64` value to enable comparison between references.
39+
40+
The surviving values are chained to the [`copied()`][copied] method, which changes the iterator of references to an iterator of the copied values.
41+
42+
The copied values ae chained to the [`collect()`][collect] method, which uses [type inference][type-inference] to return the `Vec<u64>`.
43+
44+
[sqrt]: https://doc.rust-lang.org/std/primitive.f64.html#method.sqrt
45+
[usize]: https://doc.rust-lang.org/std/primitive.usize.html
46+
[for-in-range]: https://doc.rust-lang.org/rust-by-example/flow_control/for.html
47+
[filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter
48+
[stepby]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.step_by
49+
[iter]: https://doc.rust-lang.org/core/primitive.slice.html#method.iter
50+
[filter]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.filter
51+
[closure]: https://doc.rust-lang.org/rust-by-example/fn/closures.html
52+
[collect]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.collect
53+
[copied]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.copied
54+
[type-inference]: https://doc.rust-lang.org/rust-by-example/types/inference.html
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
for i in 2..stop {
2+
if (work[i]) != 0 {
3+
for idx in (i * i..=upper_bound).step_by(i) {
4+
work[idx] = 0;
5+
}
6+
}
7+
}
8+
work.iter().filter(|num| *num != &0u64).copied().collect()
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Introduction
2+
3+
There are many ways to implement Sieve.
4+
One approach can use a couple of [`Range`][range]s to iterate the indexes of the `Vec`, both outside and inside a [`filter_map()`][filtermap] method,
5+
to process the numbers.
6+
Another approach could use a couple of [for in range][for-in-range] loops to iterate and process the numbers,
7+
and then use the [`filter()`][filter] method to return the `Vec`.
8+
9+
## General guidance
10+
11+
One of the things to try to minimize is [mutability][mutability].
12+
If using `mut` for anything than the `Vec`, then the solution may use more mutability than required.
13+
14+
## Approach: `Range`s and `filter-map()`
15+
16+
```rust
17+
pub fn primes_up_to(upper_bound: u64) -> Vec<u64> {
18+
let mut numbers: Vec<_> = (0..=upper_bound).map(Option::from).collect();
19+
let upper_bound = upper_bound as usize;
20+
(2..numbers.len())
21+
.filter_map(|i| {
22+
let prime = numbers[i].take()? as usize;
23+
(prime + prime..=upper_bound)
24+
.step_by(prime)
25+
.for_each(|j| numbers[j] = None);
26+
Some(prime as u64)
27+
})
28+
.collect()
29+
}
30+
```
31+
32+
For more information, check the [`Range`s and `filter_map()` approach][approach-ranges-and-filtermap].
33+
34+
35+
## Approach: `for` in ranges with `filter()`
36+
37+
```rust
38+
pub fn primes_up_to(upper_bound: u64) -> Vec<u64> {
39+
let mut work: Vec<u64> = (0..=upper_bound).collect();
40+
work[1] = 0;
41+
let stop = (upper_bound as f64).sqrt() as usize + 1usize;
42+
let upper_bound = upper_bound as usize;
43+
for i in 2..stop {
44+
if (work[i]) != 0 {
45+
for idx in (i * i..=upper_bound).step_by(i) {
46+
work[idx] = 0;
47+
}
48+
}
49+
}
50+
work.iter().filter(|num| *num != &0u64).copied().collect()
51+
}
52+
```
53+
54+
For more information, check the [`for` in ranges with `filter()` approach][approach-for-in-ranges-with-filter].
55+
56+
## Which approach to use?
57+
58+
The `filter_map` approach may be considered more idiomatic.
59+
Using the following benchmark, the `filter_map()` approach was also faster.
60+
61+
```rust
62+
#[bench]
63+
fn limit_of_1000(b: &mut Bencher) {
64+
b.iter(|| primes_up_to(1000));
65+
}
66+
```
67+
68+
Results
69+
70+
```
71+
// range and filter_map
72+
test limit_of_1000 ... bench: 5,164 ns/iter (+/- 206)
73+
74+
// for in ranges with filter
75+
test limit_of_1000 ... bench: 5,945 ns/iter (+/- 310)
76+
```
77+
78+
[range]: https://doc.rust-lang.org/reference/expressions/range-expr.html
79+
[filtermap]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map
80+
[for-in-range]: https://doc.rust-lang.org/rust-by-example/flow_control/for.html
81+
[filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter
82+
[mutability]: https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/mutability.html
83+
[approach-ranges-and-filtermap]: https://exercism.org/tracks/rust/exercises/sieve/approaches/ranges-and-filtermap
84+
[approach-for-in-ranges-with-filter]: https://exercism.org/tracks/rust/exercises/sieve/approaches/for-in-ranges-with-filter
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# `Range`s and `filter-map()`
2+
3+
```rust
4+
pub fn primes_up_to(upper_bound: u64) -> Vec<u64> {
5+
let mut numbers: Vec<_> = (0..=upper_bound).map(Option::from).collect();
6+
let upper_bound = upper_bound as usize;
7+
(2..numbers.len())
8+
.filter_map(|i| {
9+
let prime = numbers[i].take()? as usize;
10+
(prime + prime..=upper_bound)
11+
.step_by(prime)
12+
.for_each(|j| numbers[j] = None);
13+
Some(prime as u64)
14+
})
15+
.collect()
16+
}
17+
```
18+
19+
This approach starts by defining a `Vec` of [`Some`][some] values from `0` through the upper bound.
20+
To minimize type conversions, the upper bound is redefined as a [`usize`][usize].
21+
22+
A [Range][range] is defined that goes from `2` through the length of the `Vec`.
23+
Each number from the range is passed to the [`filter_map()`][filtermap].
24+
25+
The [closure][closure] (also known as a lambda) in the body of the `filter_map()` uses the [`take()`][take] method, combined with the
26+
unwrap operator (`?`), to get the element value in the `Vec` at the index of the number passed in from the range.
27+
If the element value is `None`, then no further processing happens in the lambda for that iteration.
28+
If the element value is `Some` number, then an inner range is defined, starting from the element value plus itself and going through the upper bound.
29+
30+
The [`step_by()`][stepby] method is used to traverse the range in steps the size of the element value.
31+
32+
Each number from the inner range is passed to the [`for_each()`][foreach] method.
33+
The lambda inside the `for_each` sets `None` as the value for the element in the `Vec` at the index of the value passed in.
34+
35+
After the inner range is done, the `filter_map()` lambda returns the element value rewrapped in a `Some` as type `u64`.
36+
37+
After the outer range is done, all of the `Some` values are [collect][collect]ed from the `Vec` and returned.
38+
[Type inference][type-inference] is used to return the values as a type `Vec<u64>`.
39+
40+
[some]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some
41+
[none]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None
42+
[usize]: https://doc.rust-lang.org/std/primitive.usize.html
43+
[range]: https://doc.rust-lang.org/reference/expressions/range-expr.html
44+
[filtermap]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.filter_map
45+
[closure]: https://doc.rust-lang.org/rust-by-example/fn/closures.html
46+
[take]: https://doc.rust-lang.org/core/option/enum.Option.html#method.take
47+
[stepby]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.step_by
48+
[foreach]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.for_each
49+
[type-inference]: https://doc.rust-lang.org/rust-by-example/types/inference.html
50+
[collect]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.collect
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(2..numbers.len())
2+
.filter_map(|i| {
3+
let prime = numbers[i].take()? as usize;
4+
(prime + prime..=upper_bound)
5+
.step_by(prime)
6+
.for_each(|j| numbers[j] = None);
7+
Some(prime as u64)
8+
}).collect()

0 commit comments

Comments
 (0)