-
Notifications
You must be signed in to change notification settings - Fork 528
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sum-of-multiples: Add approaches (#1858)
- Loading branch information
1 parent
43226e1
commit 5d4a40e
Showing
6 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
exercises/practice/sum-of-multiples/.approaches/config.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"introduction": { | ||
"authors": [ | ||
"clechasseur" | ||
] | ||
}, | ||
"approaches": [ | ||
{ | ||
"uuid": "c0e599bf-a0b4-4eb3-af73-ab6c5b04dec8", | ||
"slug": "from-factors", | ||
"title": "Calculating sum from factors", | ||
"blurb": "Calculate the sum by scanning the factors and computing their multiples.", | ||
"authors": [ | ||
"clechasseur" | ||
] | ||
}, | ||
{ | ||
"uuid": "305246ad-c36a-48ae-8047-cd00a4e7a3e4", | ||
"slug": "from-range", | ||
"title": "Calculating sum by iterating the whole range", | ||
"blurb": "Calculate the sum by scanning the whole range and identifying any multiple via factors.", | ||
"authors": [ | ||
"clechasseur" | ||
] | ||
} | ||
] | ||
} |
54 changes: 54 additions & 0 deletions
54
exercises/practice/sum-of-multiples/.approaches/from-factors/content.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Calculating sum from factors | ||
|
||
```rust | ||
pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { | ||
let mut multiples: Vec<_> = factors | ||
.iter() | ||
.filter(|&&factor| factor != 0) | ||
.flat_map(|&factor| (factor..limit).step_by(factor as usize)) | ||
.collect(); | ||
multiples.sort(); | ||
multiples.dedup(); | ||
multiples.iter().sum() | ||
} | ||
``` | ||
|
||
This approach implements the exact steps outlined in the exercise description: | ||
|
||
1. For each non-zero factor, find all multiples of that factor that are less than the `limit` | ||
2. Collect all multiples in a [`Vec`][vec] | ||
3. Remove duplicate multiples | ||
3. Calculate the sum of all unique multiples | ||
|
||
In order to compute the list of multiples for a factor, we create a [`Range`][range] from the factor (inclusive) to the `limit` (exclusive), then use [`step_by`][iterator-step_by] with the same factor. | ||
|
||
To combine the multiples of all factors, we iterate the list of factors and use [`flat_map`][iterator-flat_map] on each factor's multiples. | ||
[`flat_map`][iterator-flat_map] is a combination of [`map`][iterator-map] and [`flatten`][iterator-flatten]; it maps each factor into its multiples, then flattens them all in a single sequence. | ||
|
||
Since we need to have unique multiples to compute the proper sum, we [`collect`][iterator-collect] the multiples into a [`Vec`][vec], which allows us to then [`sort`][slice-sort][^1] them and use [`dedup`][vec-dedup] to remove the duplicates. | ||
[`collect`][iterator-collect] is a powerful function that can collect the data in a sequence and store it in any kind of collection - however, because of this, the compiler is not able to infer the type of collection you want as the output. | ||
To solve this problem, we type the `multiples` variable explicitly. | ||
|
||
Finally, calculating the sum of the remaining unique multiples in the set is easy: we can simply call [`sum`][iterator-sum]. | ||
|
||
[^1]: There is another method available to sort a slice: [`sort_unstable`][slice-sort_unstable]. Usually, using [`sort_unstable`][slice-sort_unstable] is recommended if we do not need to keep the ordering of duplicate elements (which is our case). However, [`sort`][slice-sort] has the advantage because of its implementation. From the documentation: | ||
|
||
> Current implementation | ||
> | ||
> The current algorithm is an adaptive, iterative merge sort inspired by timsort. It is designed to be very fast in cases where the slice is nearly sorted, or consists of two or more sorted sequences concatenated one after another. | ||
The last part is key, because this is exactly our use case: we concatenate sequences of _sorted_ multiples. | ||
|
||
Running a benchmark using the two methods shows that in our scenario, [`sort`][slice-sort] is about twice as fast as [`sort_unstable`][slice-sort_unstable]. | ||
|
||
[vec]: https://doc.rust-lang.org/std/vec/struct.Vec.html | ||
[range]: https://doc.rust-lang.org/std/ops/struct.Range.html | ||
[iterator-step_by]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.step_by | ||
[iterator-flat_map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flat_map | ||
[iterator-map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map | ||
[iterator-flatten]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flatten | ||
[iterator-collect]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect | ||
[slice-sort]: https://doc.rust-lang.org/std/primitive.slice.html#method.sort | ||
[vec-dedup]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.dedup | ||
[iterator-sum]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.sum | ||
[slice-sort_unstable]: https://doc.rust-lang.org/std/primitive.slice.html#method.sort_unstable |
8 changes: 8 additions & 0 deletions
8
exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { | ||
let mut multiples: Vec<_> = factors.iter() | ||
.filter(|&&factor| factor != 0) | ||
.flat_map(|&factor| (factor..limit).step_by(factor as usize)) | ||
.collect(); | ||
multiples.sort(); | ||
multiples.dedup(); | ||
multiples.iter().sum() |
25 changes: 25 additions & 0 deletions
25
exercises/practice/sum-of-multiples/.approaches/from-range/content.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Calculating sum by iterating the whole range | ||
|
||
```rust | ||
pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { | ||
(1..limit) | ||
.filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) | ||
.sum() | ||
} | ||
``` | ||
|
||
Instead of implementing the steps in the exercise description, this approach uses another angle: | ||
|
||
1. Iterate all numbers between 1 (inclusive) and `limit` (exclusive) | ||
2. Keep only numbers which have at least one factor in `factors` (automatically avoiding any duplicates) | ||
3. Calculate the sum of all numbers kept | ||
|
||
After creating our range, we use [`filter`][iterator-filter] to keep only matching multiples. | ||
To detect the multiples, we scan the `factors` and use [`any`][iterator-any] to check if at least one is a factor of the number we're checking. | ||
([`any`][iterator-any] is short-circuiting: it stops as soon as it finds one compatible factor.) | ||
|
||
Finally, once we have the multiples, we can compute the sum easily using [`sum`][iterator-sum]. | ||
|
||
[iterator-filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter | ||
[iterator-any]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any | ||
[iterator-sum]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.sum |
5 changes: 5 additions & 0 deletions
5
exercises/practice/sum-of-multiples/.approaches/from-range/snippet.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { | ||
(1..limit) | ||
.filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) | ||
.sum() | ||
} |
55 changes: 55 additions & 0 deletions
55
exercises/practice/sum-of-multiples/.approaches/introduction.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Introduction | ||
|
||
There are a couple of different approaches available to solve Sum of Multiples. | ||
One is to follow the algorithm [outlined in the exercise description][approach-from-factors], | ||
but there are other ways, including [scanning the entire range][approach-from-range]. | ||
|
||
## General guidance | ||
|
||
The key to solving Sum of Multiples is to find the unique multiples of all provided factors. | ||
To determine if `f` is a factor of a given number `n`, we can use the [remainder operator][rem]. | ||
It is also possible to find the multiples by simple addition, starting from the factor. | ||
|
||
## Approach: Calculating sum from factors | ||
|
||
```rust | ||
pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { | ||
let mut multiples: Vec<_> = factors | ||
.iter() | ||
.filter(|&&factor| factor != 0) | ||
.flat_map(|&factor| (factor..limit).step_by(factor as usize)) | ||
.collect(); | ||
multiples.sort(); | ||
multiples.dedup(); | ||
multiples.iter().sum() | ||
} | ||
``` | ||
|
||
For more information, check the [Sum from factors approach][approach-from-factors]. | ||
|
||
## Approach: Calculating sum by iterating the whole range | ||
|
||
```rust | ||
pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { | ||
(1..limit) | ||
.filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) | ||
.sum() | ||
} | ||
``` | ||
|
||
For more information, check the [Sum by iterating the whole range approach][approach-from-range]. | ||
|
||
## Which approach to use? | ||
|
||
- Computing the sum from factors can be efficient if we have a small number of factors and/or if they are large compared to the limit, because this will result in a small number of multiples to deduplicate. | ||
However, as the number of multiples grows, this approach can result in a lot of work to deduplicate them. | ||
- Computing the sum by iterating the whole range is less efficient for large ranges when the number of factors is small and/or when they are large. | ||
However, this approach has the advantage of having stable complexity that is only dependent on the limit and the number of factors, since there is no deduplication involved. | ||
It also avoids any additional memory allocation. | ||
|
||
Without proper benchmarks, the second approach may be preferred since it offers a more stable level of complexity (e.g. its performances varies less when the size of the input changes). | ||
However, if you have some knowledge of the size and shape of the input, then benchmarking might reveal that one approach is better than the other for your specific use case. | ||
|
||
[approach-from-factors]: https://exercism.org/tracks/rust/exercises/sum-of-multiples/approaches/from-factors | ||
[approach-from-range]: https://exercism.org/tracks/rust/exercises/sum-of-multiples/approaches/from-range | ||
[rem]: https://doc.rust-lang.org/core/ops/trait.Rem.html |