Skip to content

Commit 6e0035f

Browse files
authored
grains: add approaches (#1579)
grains: add approaches
1 parent 787dd20 commit 6e0035f

File tree

12 files changed

+396
-0
lines changed

12 files changed

+396
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Bit-shifting
2+
3+
```rust
4+
pub fn square(s: u32) -> u64 {
5+
if (s < 1) | (s > 64) {
6+
panic!("Square must be between 1 and 64");
7+
}
8+
1 << (s - 1)
9+
}
10+
11+
pub fn total() -> u64 {
12+
((1_u128 << 64) - 1) as u64
13+
}
14+
```
15+
16+
Instead of using math to calculate the number of grains on a square, you can simply set a bit in the correct position of a [`u64`][u64] or [`u128`][u128] value.
17+
18+
To understand how this works, consider just two squares that are represented in binary bits as `00`.
19+
20+
You use the [left-shift operator][left-shift-operator] to set `1` at the position needed to make the correct decimal value.
21+
- To set the one grain on Square One you shift `1` for `0` positions to the left.
22+
So, if `n` is `1` for square One, you subtract `n` by `1` to get `0`, which will not move it any positions to the left.
23+
The result is binary `01`, which is decimal `1`.
24+
- To set the two grains on Square Two you shift `1` for `1` position to the left.
25+
So, if `n` is `2` for square Two, you subtract `n` by `1` to get `1`, which will move it `1` position to the left.
26+
The result is binary `10`, which is decimal `2`.
27+
28+
| Square | Shift Left By | Binary Value | Decimal Value |
29+
| ------- | ------------- | ------------ | ------------- |
30+
| 1 | 0 | 0001 | 1 |
31+
| 2 | 1 | 0010 | 2 |
32+
| 3 | 2 | 0100 | 4 |
33+
| 4 | 3 | 1000 | 8 |
34+
35+
For `total` we want all of the 64 bits set to `1` to get the sum of grains on all sixty-four squares.
36+
The easy way to do this is to set the 65th bit to `1` and then subtract `1`.
37+
However, we can't do this with a `u64` which has only `64` bits, so we need to use a `u128`.
38+
To go back to our two-square example, if we can grow to three squares, then we can shift `1_u128` two positions to the left for binary `100`,
39+
which is decimal `4`.
40+
41+
```exercism/note
42+
Note that the type of a binding can be defined by appending it to the binding name.
43+
It can optionally be appended using one or more `_` separators between the value and the type.
44+
More info can be found in the [Literals](https://doc.rust-lang.org/rust-by-example/types/literals.html) section of
45+
[Rust by Example](https://doc.rust-lang.org/rust-by-example/index.html)
46+
```
47+
By subtracting `1` we get `3`, which is the total amount of grains on the two squares.
48+
49+
| Square | Binary Value | Decimal Value |
50+
| ------- | ------------ | ------------- |
51+
| 3 | 0100 | 4 |
52+
53+
| Square | Sum Binary Value | Sum Decimal Value |
54+
| ------- | ---------------- | ----------------- |
55+
| 1 | 0001 | 1 |
56+
| 2 | 0011 | 3 |
57+
58+
[u64]: https://doc.rust-lang.org/std/primitive.u64.html
59+
[u128]: https://doc.rust-lang.org/std/primitive.u128.html
60+
[left-shift-operator]: https://doc.rust-lang.org/std/ops/trait.Shl.html
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub fn square(s: u32) -> u64 {
2+
// code snipped
3+
1 << (s - 1)
4+
}
5+
6+
pub fn total() -> u64 {
7+
((1_u128 << 64) - 1) as u64
8+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"introduction": {
3+
"authors": ["bobahop"]
4+
},
5+
"approaches": [
6+
{
7+
"uuid": "319aa2eb-512d-4133-b3da-7df83e48c87f",
8+
"slug": "pow",
9+
"title": "pow",
10+
"blurb": "Use pow to raise 2 by a specified power.",
11+
"authors": ["bobahop"]
12+
},
13+
{
14+
"uuid": "84e5365f-3928-46b4-9750-49575dc924ae",
15+
"slug": "bit-shifting",
16+
"title": "Bit-shifting",
17+
"blurb": "Use bit-shifting to raise 2 by a specified power.",
18+
"authors": ["bobahop"]
19+
},
20+
{
21+
"uuid": "c245c1f7-7d8c-4a73-81c4-d91ff50a5986",
22+
"slug": "max-value",
23+
"title": "Max value",
24+
"blurb": "Use u64::MAX for total.",
25+
"authors": ["bobahop"]
26+
}
27+
]
28+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Introduction
2+
3+
There are various idiomatic approaches to solve Grains.
4+
You can use `pow` to calculate the number on grains on a square.
5+
Or you can use bit-shifting.
6+
7+
## General guidance
8+
9+
The key to solving Grains is to focus on each square having double the amount of grains as the square before it.
10+
This means that the amount of grains grows exponentially.
11+
The first square has one grain, which is `2` to the power of `0`.
12+
The second square has two grains, which is `2` to the power of `1`.
13+
The third square has four grains, which is `2` to the power of `2`.
14+
You can see that the exponent, or power, that `2` is raised by is always one less than the square number.
15+
16+
| Square | Power | Value |
17+
| ------- | ---------- | ----------------------- |
18+
| 1 | 0 | 2 to the power of 0 = 1 |
19+
| 2 | 1 | 2 to the power of 1 = 2 |
20+
| 3 | 2 | 2 to the power of 2 = 4 |
21+
| 4 | 3 | 2 to the power of 4 = 8 |
22+
23+
## Approach: `pow`
24+
25+
```rust
26+
pub fn square(s: u32) -> u64 {
27+
if (s < 1) | (s > 64) {
28+
panic!("Square must be between 1 and 64");
29+
}
30+
2u64.pow(s - 1)
31+
}
32+
33+
pub fn total() -> u64 {
34+
(2_u128.pow(64) - 1) as u64
35+
}
36+
```
37+
38+
For more information, check the [`pow` approach][approach-pow].
39+
40+
## Approach: Bit-shifting
41+
42+
```rust
43+
pub fn square(s: u32) -> u64 {
44+
if (s < 1) | (s > 64) {
45+
panic!("Square must be between 1 and 64");
46+
}
47+
1 << (s - 1)
48+
}
49+
50+
pub fn total() -> u64 {
51+
((1_u128 << 64) - 1) as u64
52+
}
53+
```
54+
55+
For more information, check the [Bit-shifting approach][approach-bit-shifting].
56+
57+
## Other approaches
58+
59+
Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows:
60+
61+
### Other approach: Max value
62+
63+
The maximum value for an unsigned integer gives the total number of grains on all sixty-four squares.
64+
For more information, check the [`u64::MAX` for Total Approach][approach-max-value].
65+
66+
## Which approach to use?
67+
68+
`pow` may be considered the most readable solution.
69+
70+
Bit-shifting is the same measurable performance as `pow`, but may be considered less readable.
71+
72+
For more information, check the [Performance article][article-performance].
73+
74+
[approach-pow]: https://exercism.org/tracks/rust/exercises/grains/approaches/pow
75+
[approach-bit-shifting]: https://exercism.org/tracks/rust/exercises/grains/approaches/bit-shifting
76+
[approach-max-value]: https://exercism.org/tracks/rust/exercises/grains/approaches/max-value
77+
[article-performance]: https://exercism.org/tracks/rust/exercises/grains/articles/performance
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# `u64::MAX` for total
2+
3+
```rust
4+
pub fn total() -> u64 {
5+
u64::MAX
6+
}
7+
```
8+
9+
The maximum value for an unsigned integer gives the total number of grains on all sixty-four squares.
10+
It is essentially getting all of the bits of a 64-bit number set to `1`.
11+
12+
This might seem like a "cheat".
13+
To understand how it works, consider just two squares that are represented in bits as `00`.
14+
15+
- If there is a grain on square One, then the first bit (on the right) is set to binary `01`, which is the decimal value of `1`.
16+
- If there are two grains on square Two, then the second bit (from the right) is set to binary `10`, which is the decimal value of `2`.
17+
- If there are is one grain on Square One and two grains on square Two, then both bits are set to binary `11`, which is the decimal value of `3`.
18+
- So, with all of the two bits set you get `3`, which is the number of all grains on both squares.
19+
20+
Getting the value of [`u64::MAX`][u64-max] is getting 64 bits set to `1`, which is just what we need to get the total number of grains on
21+
sixty-four squares, given that the grains double on each square, starting with one grain on the first square.
22+
23+
[u64-max]: https://doc.rust-lang.org/std/u64/constant.MAX.html
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn total() -> u64 {
2+
u64::MAX
3+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
## `pow`
2+
3+
```rust
4+
pub fn square(s: u32) -> u64 {
5+
if (s < 1) | (s > 64) {
6+
panic!("Square must be between 1 and 64");
7+
}
8+
2u64.pow(s - 1)
9+
}
10+
11+
pub fn total() -> u64 {
12+
(2_u128.pow(64) - 1) as u64
13+
}
14+
```
15+
16+
Other languages may have an exponential operator such as `**` or `^` to raise a number by a specified power.
17+
Rust does not have an exponential operator, but uses the [`pow`][pow-u64] method.
18+
19+
`pow` is nicely suited to the problem, since we start with one grain and keep doubling the number of grains on each successive square.
20+
`1` grain is `2u64.pow(0)`, `2` grains is `2u64.pow(1)`, `4` is `2u64.pow(2)`, and so on.
21+
22+
```exercism/note
23+
Note that the type of a binding can be defined by appending it to the binding name.
24+
It can optionally be appended using one or more `_` separators between the value and the type.
25+
More info can be found in the [Literals](https://doc.rust-lang.org/rust-by-example/types/literals.html) section of
26+
[Rust by Example](https://doc.rust-lang.org/rust-by-example/index.html)
27+
```
28+
29+
So, to get the right exponent, we subtract `1` from the square number `s`.
30+
31+
The easiest way to get `total` is to use [`u128::pow`][pow-u128] to get the value for an imaginary 65th square,
32+
and then subtract `1` from it.
33+
To understand how that works, consider a board that has only two squares.
34+
If we could grow the board to three squares, then we could get the number of grains on the imaginary third square,
35+
which would be `4.`
36+
You could then subtract `4` by `1` to get `3`, which is the number of grains on the first square (`1`) and the second square (`2`).
37+
Remembering that the exponent must be one less than the square you want,
38+
you might like to call `2u64.pow(64)` to get the number of grains on the imaginary 65th square.
39+
However, that won't work, because the resulting number is too large for a `u64`.
40+
So, you can use `2u128.pow(64)`, and when it is subtracted by `1` it will then fit in the `u64` value which is returned.
41+
42+
[pow-u64]: https://doc.rust-lang.org/std/primitive.u64.html#method.pow
43+
[pow-u128]: https://doc.rust-lang.org/std/primitive.u128.html#method.pow
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub fn square(s: u32) -> u64 {
2+
// code snipped
3+
2u64.pow(s - 1)
4+
}
5+
6+
pub fn total() -> u64 {
7+
(2_u128.pow(64) - 1) as u64
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"articles": [
3+
{
4+
"uuid": "6f224b6e-6cbf-4ff6-9618-4423435084aa",
5+
"slug": "performance",
6+
"title": "Performance deep dive",
7+
"blurb": "Deep dive to find out the most performant approach to calculating Grains.",
8+
"authors": ["bobahop"]
9+
}
10+
]
11+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#![feature(test)]
2+
extern crate test;
3+
use test::Bencher;
4+
5+
fn main() {
6+
println!("Hello, world!");
7+
}
8+
9+
pub fn square_bit(s: u32) -> u64 {
10+
if (s < 1) | (s > 64) {
11+
panic!("Square must be between 1 and 64");
12+
}
13+
1 << (s - 1)
14+
}
15+
16+
pub fn total_bit() -> u64 {
17+
((1_u128 << 64) - 1) as u64
18+
}
19+
20+
pub fn square_pow(s: u32) -> u64 {
21+
if (s < 1) | (s > 64) {
22+
panic!("Square must be between 1 and 64");
23+
}
24+
2u64.pow(s - 1)
25+
}
26+
27+
pub fn total_pow_u128() -> u64 {
28+
(2_u128.pow(64) - 1) as u64
29+
}
30+
31+
pub fn total_pow_fold() -> u64 {
32+
(1_u32..=64_u32).fold(0u64, |total, num| total + square_pow(num))
33+
}
34+
35+
pub fn total_pow_for() -> u64 {
36+
let mut accum = 0;
37+
for i in 1..=64 {
38+
accum += square_pow(i)
39+
}
40+
accum
41+
}
42+
43+
#[bench]
44+
fn test_square_bit(b: &mut Bencher) {
45+
b.iter(|| square_bit(64));
46+
}
47+
48+
#[bench]
49+
fn test_total_bit(b: &mut Bencher) {
50+
b.iter(|| total_bit());
51+
}
52+
53+
#[bench]
54+
fn test_square_pow(b: &mut Bencher) {
55+
b.iter(|| square_pow(64));
56+
}
57+
58+
#[bench]
59+
fn test_total_pow_u128(b: &mut Bencher) {
60+
b.iter(|| total_pow_u128());
61+
}
62+
63+
#[bench]
64+
fn test_total_pow_fold(b: &mut Bencher) {
65+
b.iter(|| total_pow_fold());
66+
}
67+
68+
#[bench]
69+
fn test_total_pow_for(b: &mut Bencher) {
70+
b.iter(|| total_pow_for());
71+
}

0 commit comments

Comments
 (0)