Skip to content

Commit 1bbd99e

Browse files
authored
Grains approaches (exercism#2234)
* Create config.json * Create introduction.md * Create snippet.txt * Create content.md * Create snippet.txt * Create content.md
1 parent 12e5ad5 commit 1bbd99e

6 files changed

Lines changed: 205 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Bit-shifting
2+
3+
```java
4+
import java.math.BigInteger;
5+
6+
class Grains {
7+
8+
BigInteger grainsOnSquare(final int square) {
9+
if (square < 1 || square > 64) {
10+
throw new IllegalArgumentException("square must be between 1 and 64");
11+
}
12+
return BigInteger.ONE.shiftLeft(square - 1);
13+
}
14+
15+
BigInteger grainsOnBoard() {
16+
return BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE);
17+
}
18+
}
19+
```
20+
21+
To calculate the grains on a square you can set a bit in the correct position of a [`BigInteger`][biginteger] value.
22+
23+
To understand how this works, consider just two squares that are represented in binary bits as `00`.
24+
25+
You use the [`BigInteger.shiftLeft()`][shiftleft] method to set `1` (i.e. [`BigInteger.ONE`][one]) at the position needed to make the correct decimal value.
26+
- To set the one grain on Square One you shift `1` for `0` positions to the left.
27+
So, if `square` is `1` for square One, you subtract `square` by `1` to get `0`, which will not move it any positions to the left.
28+
The result is binary `01`, which is decimal `1`.
29+
- To set the two grains on Square Two you shift `1` for `1` position to the left.
30+
So, if `square` is `2` for square Two, you subtract `square` by `1` to get `1`, which will move it `1` position to the left.
31+
The result is binary `10`, which is decimal `2`.
32+
33+
| Square | Shift Left By | Binary Value | Decimal Value |
34+
| ------- | ------------- | ------------ | ------------- |
35+
| 1 | 0 | 0001 | 1 |
36+
| 2 | 1 | 0010 | 2 |
37+
| 3 | 2 | 0100 | 4 |
38+
| 4 | 3 | 1000 | 8 |
39+
40+
For `grainsOnBoard` we want all of the 64 bits set to `1` to get the sum of grains on all sixty-four squares.
41+
The easy way to do this is to set the 65th bit to `1` and then subtract `1`.
42+
To go back to our two-square example, if we can grow to three squares, then we can shift `BigInteger.ONE` two positions to the left for binary `100`,
43+
which is decimal `4`.
44+
By subtracting `1` we get `3`, which is the total amount of grains on the two squares.
45+
46+
| Square | Binary Value | Decimal Value |
47+
| ------- | ------------ | ------------- |
48+
| 3 | 0100 | 4 |
49+
50+
| Square | Sum Binary Value | Sum Decimal Value |
51+
| ------- | ---------------- | ----------------- |
52+
| 1 | 0001 | 1 |
53+
| 2 | 0011 | 3 |
54+
55+
[biginteger]: https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html
56+
[shiftleft]: https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html#shiftLeft(int)
57+
[one]: https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html#ONE
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
BigInteger grainsOnSquare(final int square) {
2+
if (square < 1 || square > 64) {
3+
throw new IllegalArgumentException("square must be between 1 and 64");
4+
}
5+
return BigInteger.ONE.shiftLeft(square - 1);
6+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"introduction": {
3+
"authors": ["bobahop"]
4+
},
5+
"approaches": [
6+
{
7+
"uuid": "92f88a04-31e2-4ae9-8f05-55b290490ad0",
8+
"slug": "pow",
9+
"title": "Pow",
10+
"blurb": "Use BigInteger.pow to raise 2 by a specified power.",
11+
"authors": ["bobahop"]
12+
},
13+
{
14+
"uuid": "00186454-8013-4f5e-9177-cdab3ef98a31",
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+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Introduction
2+
3+
There are various idiomatic approaches to solve Grains.
4+
You can use `BigInteger.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: `BigInteger.pow()`
24+
25+
```java
26+
import java.math.BigInteger;
27+
28+
class Grains {
29+
30+
BigInteger grainsOnSquare(final int square) {
31+
if ((square <= 0) || (square >= 65)) {
32+
throw new IllegalArgumentException("square must be between 1 and 64");
33+
}
34+
return BigInteger.valueOf(2).pow(square - 1);
35+
}
36+
37+
BigInteger grainsOnBoard() {
38+
return BigInteger.valueOf(2).pow(64).subtract(BigInteger.ONE);
39+
}
40+
}
41+
```
42+
43+
For more information, check the [`BigInteger.pow()` approach][approach-pow].
44+
45+
## Approach: Bit-shifting
46+
47+
```java
48+
import java.math.BigInteger;
49+
50+
class Grains {
51+
52+
BigInteger grainsOnSquare(final int square) {
53+
if (square < 1 || square > 64) {
54+
throw new IllegalArgumentException("square must be between 1 and 64");
55+
}
56+
return BigInteger.ONE.shiftLeft(square - 1);
57+
}
58+
59+
BigInteger grainsOnBoard() {
60+
return BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE);
61+
}
62+
}
63+
```
64+
65+
For more information, check the [Bit-shifting approach][approach-bit-shifting].
66+
67+
## Which approach to use?
68+
69+
Since benchmarking with the [Java Microbenchmark Harness][jmh] is currently outside the scope of this document,
70+
the choice between `pow` and bit-shifting can be made by perceived readability.
71+
72+
[approach-pow]: https://exercism.org/tracks/java/exercises/grains/approaches/pow
73+
[approach-bit-shifting]: https://exercism.org/tracks/java/exercises/grains/approaches/bit-shifting
74+
[jmh]: https://github.com/openjdk/jmh
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# `BigInteger.pow()`
2+
3+
```java
4+
import java.math.BigInteger;
5+
6+
class Grains {
7+
8+
BigInteger grainsOnSquare(final int square) {
9+
if ((square <= 0) || (square >= 65)) {
10+
throw new IllegalArgumentException("square must be between 1 and 64");
11+
}
12+
return BigInteger.valueOf(2).pow(square - 1);
13+
}
14+
15+
BigInteger grainsOnBoard() {
16+
return BigInteger.valueOf(2).pow(64).subtract(BigInteger.ONE);
17+
}
18+
}
19+
```
20+
21+
Other languages may have an exponential operator such as `**` or `^` to raise a number by a specified power.
22+
Java does not have an exponential operator, but can use the [`BigInteger.pow()`][pow] method.
23+
24+
The `pow` method is nicely suited to the problem, since we start with one grain and keep doubling the number of grains on each successive square.
25+
`1` grain is `BigIntger.valueOf(2).pow(0)`, `2` grains is `BigIntger.valueOf(2).pow(1)`, `4` grains is `BigIntger.valueOf(2).pow(2)`, and so on.
26+
So, to get the right exponent, we subtract `1` from the `square` number.
27+
The [`valueOf`][valueof] method is used to get `2` as a `BigInteger`.
28+
29+
For `grainsOnBoard`, the easiest way to get the total grains on all 64 squares is to get the value for an imaginary 65th square,
30+
and then subtract 1 from it.
31+
To understand how that works, consider a board that has only two squares.
32+
If we could grow the board to three squares, then we could get the number of grains on the imaginary third square, which would be 4.
33+
You could then [subtract][subtract] 4 by 1 (i.e. [`BigInteger.ONE`][one]) to get 3, which is the number of grains on the first square (1) and the second square (2).
34+
Remembering that the exponent must be one less than the square you want,
35+
you can call `BigIntger.valueOf(2).pow(64)` to get the number of grains on the imaginary 65th square.
36+
Subtracting that value by 1 gives the values on all 64 squares.
37+
38+
[pow]: https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html#pow(int)
39+
[valueof]: https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html#valueOf(long)
40+
[subtract]: https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html#subtract(java.math.BigInteger)
41+
[one]: https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html#ONE
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
BigInteger grainsOnSquare(final int square) {
2+
if ((square <= 0) || (square >= 65)) {
3+
throw new IllegalArgumentException("square must be between 1 and 64");
4+
}
5+
return BigInteger.valueOf(2).pow(square - 1);
6+
}

0 commit comments

Comments
 (0)