Skip to content

Commit 7d6518e

Browse files
authored
bob: add approaches (#1574)
bob: add approaches
1 parent 3f23c66 commit 7d6518e

File tree

12 files changed

+546
-0
lines changed

12 files changed

+546
-0
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Answer array
2+
3+
```rust
4+
const ANSWERS: &'static [&'static str] = &[
5+
"Whatever.",
6+
"Sure.",
7+
"Whoa, chill out!",
8+
"Calm down, I know what I'm doing!",
9+
];
10+
11+
pub fn reply(msg: &str) -> &str {
12+
let message = msg.trim_end();
13+
if message.is_empty() {
14+
return "Fine. Be that way!";
15+
}
16+
17+
let is_questioning = if message.ends_with('?') { 1 } else { 0 };
18+
let is_yelling =
19+
if message.chars().any(|ch| ch.is_alphabetic()) && message == message.to_uppercase() {
20+
2
21+
} else {
22+
0
23+
};
24+
25+
ANSWERS[is_questioning + is_yelling]
26+
}
27+
```
28+
29+
In this approach you define an array that contains Bob’s answers, and each condition is given a score.
30+
The correct answer is selected from the array by using the score as the array index.
31+
32+
The array is defined using the [`static` lifetime annotation][static-lifetime] for a reference to an array of [`str`][str] [references][reference].
33+
`static` means that the value will live for the life of the program.
34+
The `static` annotation can be removed, as described in the [Shortening section](#shortening).
35+
36+
The reference symbols `&` are needed because the compiler must know the size of each binding.
37+
The compiler doesn't know the size of each `str`, but it does know the size of a reference to a `str`,
38+
which is the size of a pointer for that platform.
39+
For a 64-bit system, the size of a pointer is 64 bits.
40+
So, each `str` is defined as a `str` reference (`&str`).
41+
The array itself is defined as a reference, since the compiler does not know the size of the whole array and its contents.
42+
43+
- The [`str`][str] method [`trim_end`][trim-end] is used to remove whitespace from the end of the input.
44+
- If the trimmed input [`is_empty`][is-empty], then the response for nothing said is returned.
45+
- The [`ends_with`][ends-with] method is used to determine if the input ends with a question mark.
46+
If so, `is_questioning` is given the value `1`, otherwise it is given the value `0`.
47+
48+
- The first half of the yell condition
49+
50+
```rust
51+
message.chars().any(|ch| ch.is_alphabetic())
52+
```
53+
54+
calls the `str` method [`chars`][chars] to create an [`Iterator`][iterator] for the characters in the trimmed input.
55+
- The iterated characters are passed into the `Iterator` method [`any`][any].
56+
- `any` passes each character to a [closure][closure] using the [`char`][char] method [`is_alphabetic`][is-alphabetic] to ensure there is at least one letter character in the `str`.
57+
This is because the second half of the condition tests that the uppercased input is the same as the input.
58+
If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a yell.
59+
The uppercasing is done by using the `str` method [`to_uppercase`][to-uppercase].
60+
If the input is a yell, then `is_yelling` is given the value `2`, otherwise it is given the value `0`.
61+
62+
The final expression returns the value in the `ANSWERS` array at the index of the combined values for `is_questioning` and `is_yelling`.
63+
64+
```exercism/note
65+
Note that the final line is just `ANSWERS[is_questioning + is_yelling]`
66+
This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html)
67+
from a function without using `return` and a semicolon.
68+
```
69+
70+
| is_yelling | is_questioning | Index | Answer |
71+
| ---------- | -------------- | --------- | ------------------------------------- |
72+
| `false` | `false` | 0 + 0 = 0 | `"Whatever."` |
73+
| `false` | `true` | 0 + 1 = 1 | `"Sure."` |
74+
| `true` | `false` | 2 + 0 = 2 | `"Whoa, chill out!"` |
75+
| `true` | `true` | 2 + 1 = 3 | `"Calm down, I know what I'm doing!"` |
76+
77+
## Shortening
78+
79+
For defining the array the `static` lifetime specifiers can be dropped, like so
80+
81+
```rust
82+
const ANSWERS: &[&str] = &[
83+
"Whatever.",
84+
"Sure.",
85+
"Whoa, chill out!",
86+
"Calm down, I know what I'm doing!",
87+
];
88+
```
89+
90+
A lifetime specifier doesn't change the actual [lifetime][lifetime] of a binding.
91+
It just makes the lifetime explicit.
92+
When the compiler can figure out the lifetime, then the annotation can be dropped, which is called [lifetime elision][lifetime-elision].
93+
94+
[static-lifetime]: https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#the-static-lifetime
95+
[str]: https://doc.rust-lang.org/std/primitive.str.html
96+
[reference]: https://doc.rust-lang.org/std/primitive.reference.html
97+
[lifetime]: https://doc.rust-lang.org/rust-by-example/scope/lifetime.html
98+
[lifetime-elision]:https://doc.rust-lang.org/reference/lifetime-elision.html
99+
[trim-end]: https://doc.rust-lang.org/std/primitive.str.html#method.trim_end
100+
[is-empty]: https://doc.rust-lang.org/std/primitive.str.html#method.is_empty
101+
[ends-with]: https://doc.rust-lang.org/std/primitive.str.html#method.ends_with
102+
[chars]: https://doc.rust-lang.org/std/primitive.str.html#method.chars
103+
[closure]: https://doc.rust-lang.org/rust-by-example/fn/closures.html
104+
[iterator]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
105+
[any]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any
106+
[char]: https://doc.rust-lang.org/std/primitive.char.html
107+
[is-alphabetic]: https://doc.rust-lang.org/std/primitive.char.html#method.is_alphabetic
108+
[to-uppercase]: https://doc.rust-lang.org/std/primitive.str.html#method.to_uppercase
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
let is_questioning = if message.ends_with('?') { 1 } else { 0 };
2+
let is_yelling =
3+
if message.chars().any(|ch| ch.is_alphabetic()) && message == message.to_uppercase() {
4+
2
5+
} else {
6+
0
7+
};
8+
ANSWERS[is_questioning + is_yelling]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"introduction": {
3+
"authors": ["bobahop"],
4+
"contributors": []
5+
},
6+
"approaches": [
7+
{
8+
"uuid": "e10b9315-dcf5-42b8-bed9-5c0be9803101",
9+
"slug": "if-statements",
10+
"title": "If statements",
11+
"blurb": "Use if statements to return the answer.",
12+
"authors": ["bobahop"]
13+
},
14+
{
15+
"uuid": "145a190e-b9ab-4dc2-8a94-b945c35f967f",
16+
"slug": "match-on-tuple",
17+
"title": "Match on a tuple",
18+
"blurb": "Use match on a tuple to return the answer.",
19+
"authors": ["bobahop"]
20+
},
21+
{
22+
"uuid": "13e83490-9b33-407a-8d22-843aa0c351e4",
23+
"slug": "answer-array",
24+
"title": "Answer array",
25+
"blurb": "Index into an array to return the answer.",
26+
"authors": ["bobahop"]
27+
}
28+
]
29+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# `if` statements
2+
3+
```rust
4+
pub fn reply(msg: &str) -> &str {
5+
let message = msg.trim_end();
6+
if message.is_empty() {
7+
return "Fine. Be that way!";
8+
}
9+
10+
let is_questioning = message.ends_with('?');
11+
let is_yelling =
12+
message.chars().any(|ch| ch.is_alphabetic()) && message == message.to_uppercase();
13+
14+
if is_yelling {
15+
return if is_questioning {
16+
"Calm down, I know what I'm doing!"
17+
} else {
18+
"Whoa, chill out!"
19+
};
20+
}
21+
if is_questioning {
22+
return "Sure.";
23+
}
24+
"Whatever."
25+
}
26+
```
27+
28+
In this approach you have a series of `if` statements to evaluate the conditions.
29+
As soon as the right condition is found, the correct response is returned.
30+
31+
- First, the [`str`][str] method [`trim_end`][trim-end] is used to remove whitespace from the end of the input.
32+
- If the trimmed input [`is_empty`][is-empty], then the response for nothing said is returned.
33+
- The [`ends_with`][ends-with] method is used to determine if the input ends with a question mark.
34+
- The first half of the yell condition
35+
36+
```rust
37+
message.chars().any(|ch| ch.is_alphabetic())
38+
```
39+
40+
calls the `str` method [`chars`][chars] to create an [`Iterator`][iterator] for the characters in the trimmed input.
41+
- The iterated characters are passed into the `Iterator` method [`any`][any].
42+
- `any` passes each character to a [closure][closure] using the [`char`][char] method [`is_alphabetic`][is-alphabetic] to ensure there is at least one letter character in the `str`.
43+
This is because the second half of the condition tests that the uppercased input is the same as the input.
44+
If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a yell.
45+
The uppercasing is done by using the `str` method [`to_uppercase`][to-uppercase].
46+
47+
- If the input is a yell, then the function returns the response for if the input is a yelled question os just a yell.
48+
- If the input is a question, then the function returns the response for that.
49+
- Finally, if the function has not returned by the end, the response for neither a yell nor a question is returned.
50+
51+
```exercism/note
52+
Note that the final line is just `"Whatever."`
53+
This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html)
54+
from a function without using `return` and a semicolon.
55+
```
56+
57+
[str]: https://doc.rust-lang.org/std/primitive.str.html
58+
[trim-end]: https://doc.rust-lang.org/std/primitive.str.html#method.trim_end
59+
[is-empty]: https://doc.rust-lang.org/std/primitive.str.html#method.is_empty
60+
[ends-with]: https://doc.rust-lang.org/std/primitive.str.html#method.ends_with
61+
[chars]: https://doc.rust-lang.org/std/primitive.str.html#method.chars
62+
[closure]: https://doc.rust-lang.org/rust-by-example/fn/closures.html
63+
[iterator]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
64+
[any]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any
65+
[char]: https://doc.rust-lang.org/std/primitive.char.html
66+
[is-alphabetic]: https://doc.rust-lang.org/std/primitive.char.html#method.is_alphabetic
67+
[to-uppercase]: https://doc.rust-lang.org/std/primitive.str.html#method.to_uppercase
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
if is_yelling {
2+
return if is_questioning {
3+
"Calm down, I know what I'm doing!"
4+
} else {
5+
"Whoa, chill out!"
6+
};
7+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Introduction
2+
3+
There are various idiomatic approaches to solve Bob.
4+
A basic approach can use a series of `if` statements to test the conditions.
5+
Or a [match][match] on a [tuple][tuple] of the conditions can be used.
6+
An array can contain answers from which the right response is selected by an index calculated from scores given to the conditions.
7+
8+
## General guidance
9+
10+
Regardless of the approach used, some things you could look out for include
11+
12+
- If the input is trimmed, [trim_end][trim-end] only once.
13+
14+
- Use the [ends_with][ends-with] `str` method instead of checking the last character by index for `?`.
15+
16+
- Don't copy/paste the logic for determining a shout and for determing a question into determing a shouted question.
17+
Combine the two determinations instead of copying them.
18+
Not duplicating the code will keep the code [DRY][dry].
19+
20+
- Perhaps consider making `IsQuestion` and `IsShout` values set once instead of functions that are possibly called twice.
21+
22+
- If an `if` statement can return, then an `else if` or `else` is not needed.
23+
Execution will either return or will continue to the next statement anyway.
24+
25+
## Approach: `if` statements
26+
27+
```rust
28+
pub fn reply(msg: &str) -> &str {
29+
let message = msg.trim_end();
30+
if message.is_empty() {
31+
return "Fine. Be that way!";
32+
}
33+
34+
let is_questioning = message.ends_with('?');
35+
let is_yelling =
36+
message.chars().any(|ch| ch.is_alphabetic()) && message == message.to_uppercase();
37+
38+
if is_yelling {
39+
return if is_questioning {
40+
"Calm down, I know what I'm doing!"
41+
} else {
42+
"Whoa, chill out!"
43+
};
44+
}
45+
if is_questioning {
46+
return "Sure.";
47+
}
48+
"Whatever."
49+
}
50+
```
51+
52+
For more information, check the [`if` statements approach][approach-if].
53+
54+
## Approach: `match` on a `tuple`
55+
56+
```rust
57+
pub fn reply(msg: &str) -> &str {
58+
let message = msg.trim_end();
59+
if message.is_empty() {
60+
return "Fine. Be that way!";
61+
}
62+
63+
let is_questioning = message.ends_with('?');
64+
let is_yelling =
65+
message.chars().any(|ch| ch.is_alphabetic()) && message == message.to_uppercase();
66+
67+
match (is_yelling, is_questioning) {
68+
(true, true) => "Calm down, I know what I'm doing!",
69+
(true, _) => "Whoa, chill out!",
70+
(_, true) => "Sure.",
71+
_ => "Whatever.",
72+
}
73+
}
74+
```
75+
76+
For more information, check the [`match` on a `tuple`][approach-match].
77+
78+
## Other approaches
79+
80+
Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows:
81+
82+
### Other approach: answer array
83+
84+
An array can be defined that contains Bob’s answers, and each condition is given a score.
85+
The correct answer is selected from the array by using the score as the array index.
86+
For more information, check the [Answer array approach][approach-answer-array].
87+
88+
## Which approach to use?
89+
90+
Which to use is pretty much a matter of personal preference,
91+
but the `if` statements and `match` approaches may be considered to be more idiomatic.
92+
93+
- To compare the performance of the approaches, check out the [Performance article][article-performance].
94+
95+
[trim-end]: https://doc.rust-lang.org/std/string/struct.String.html#method.trim_end
96+
[ends-with]: https://doc.rust-lang.org/std/primitive.str.html#method.ends_with
97+
[match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html
98+
[tuple]: https://doc.rust-lang.org/rust-by-example/primitives/tuples.html
99+
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
100+
[approach-if]: https://exercism.org/tracks/rust/exercises/bob/approaches/if-statements
101+
[approach-match]: https://exercism.org/tracks/rust/exercises/bob/approaches/match-on-tuple
102+
[approach-answer-array]: https://exercism.org/tracks/rust/exercises/bob/approaches/answer-array
103+
[article-performance]: https://exercism.org/tracks/rust/exercises/bob/articles/performance

0 commit comments

Comments
 (0)