Introduction
Bob is a lackadaisical teenager.
He likes to think that he’s very cool.
And he definitely doesn’t get excited about things.
That wouldn’t be cool.
When people talk to him, his responses are pretty limited.
Instructions
Instructions
Your task is to determine what Bob will reply to someone when they say something to him or ask him a question.
Bob only ever answers one of five things:
- “Sure.”
This is his response if you ask him a question, such as “How are you?”
The convention used for questions is that it ends with a question mark.
- “Whoa, chill out!”
This is his answer if you YELL AT HIM.
The convention used for yelling is ALL CAPITAL LETTERS.
- “Calm down, I know what I’m doing!”
This is what he says if you yell a question at him.
- “Fine. Be that way!”
This is how he responds to silence.
The convention used for silence is nothing, or various combinations of whitespace characters.
- “Whatever.”
This is what he answers to anything else.
Dig Deeper
If statements
if statements
pub fn reply(msg: &str) -> &str {
let message = msg.trim_end();
if message.is_empty() {
return "Fine. Be that way!";
}
let is_questioning = message.ends_with('?');
let is_yelling =
message.chars().any(|ch| ch.is_alphabetic()) && message == message.to_uppercase();
if is_yelling {
return if is_questioning {
"Calm down, I know what I'm doing!"
} else {
"Whoa, chill out!"
};
}
if is_questioning {
return "Sure.";
}
"Whatever."
}
In this approach you have a series of if statements to evaluate the conditions.
As soon as the right condition is found, the correct response is returned.
- First, the
str method trim_end is used to remove whitespace from the end of the input.
- If the trimmed input
is_empty, then the response for nothing said is returned.
- The
ends_with method is used to determine if the input ends with a question mark.
- The first half of the yell condition
message.chars().any(|ch| ch.is_alphabetic())
calls the str method chars to create an Iterator for the characters in the trimmed input.
-
The iterated characters are passed into the Iterator method any.
-
any passes each character to a closure using the char method is_alphabetic to ensure there is at least one letter character in the str.
This is because the second half of the condition tests that the uppercased input is the same as the input.
If the input were only "123" it would equal itself uppercased, but without letters it would not be a yell.
The uppercasing is done by using the str method to_uppercase.
-
If the input is a yell, then the function returns the response for if the input is a yelled question os just a yell.
-
If the input is a question, then the function returns the response for that.
-
Finally, if the function has not returned by the end, the response for neither a yell nor a question is returned.
Note that the final line is just `"Whatever."`
This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html)
from a function without using `return` and a semicolon.
Match on a tuple
match on a tuple
pub fn reply(msg: &str) -> &str {
let message = msg.trim_end();
if message.is_empty() {
return "Fine. Be that way!";
}
let is_questioning = message.ends_with('?');
let is_yelling =
message.chars().any(|ch| ch.is_alphabetic()) && message == message.to_uppercase();
match (is_yelling, is_questioning) {
(true, true) => "Calm down, I know what I'm doing!",
(true, _) => "Whoa, chill out!",
(_, true) => "Sure.",
_ => "Whatever.",
}
}
In this approach you have a match expression to evaluate the conditions.
- First, the
str method trim_end is used to remove whitespace from the end of the input.
- If the trimmed input
is_empty, then the response for nothing said is returned.
- The
ends_with method is used to determine if the input ends with a question mark.
- The first half of the yell condition
message.chars().any(|ch| ch.is_alphabetic())
calls the str method chars to create an Iterator for the characters in the trimmed input.
- The iterated characters are passed into the
Iterator method any.
any passes each character to a closure using the char method is_alphabetic to ensure there is at least one letter character in the str.
This is because the second half of the condition tests that the uppercased input is the same as the input.
If the input were only "123" it would equal itself uppercased, but without letters it would not be a yell.
The uppercasing is done by using the str method to_uppercase.
The match tests a tuple constructed from the values for being a question and being a yell.
Since those values are booleans, each arm of the match tests a pattern of boolean values in the tuple positions.
- If both the yell value in the tuple is
true and the question part of the tuple is true,
then the response is returned for a yelled question.
Note that each arm of the `match` is a single-line expression, so `return` is not needed in the responses returned by the `match` arms.
And, since the `match` is the last expression in the function, `return` is not used before `match`.
The [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html)
from a function without using `return` and a semicolon.
- If the tuple is not
(true, true), then the next arm of the match tests if the yell value in the tuple is true.
It uses the wildcard pattern of _ to disregard the value for the question part of the tuple.
If the pattern matches, in other words, if the yell value in the tuple is true, then the match returns the response for a yell.
- If the trimmed input is not a yell, then the next arm of the
match tests if it is a question.
- If so, then the function returns the response for a question.
- Finally, the wildcard pattern is applied to the whole tuple.
This is similar to
default used in switch statements in other languages.
It returns “Whatever.” no matter what the values in the tuple are.
Note that a `match` in Rust must be exhaustive.
This means it must match all conceivable patterns of the value being tested or the code will not compile.
For a boolean value, you can have one arm to match `true` and another to match `false`,
and the compiler will know that the `match` has checked all conceivable patterns for a boolean.
For a value with many possible patterns, such as a `u32`, you can have each arm match whatever patterns you care about,
such as `1` and `2`, and then have one final arm using the wildcard pattern to match on everything else.
Answer array
Answer array
const ANSWERS: &'static [&'static str] = &[
"Whatever.",
"Sure.",
"Whoa, chill out!",
"Calm down, I know what I'm doing!",
];
pub fn reply(msg: &str) -> &str {
let message = msg.trim_end();
if message.is_empty() {
return "Fine. Be that way!";
}
let is_questioning = if message.ends_with('?') { 1 } else { 0 };
let is_yelling =
if message.chars().any(|ch| ch.is_alphabetic()) && message == message.to_uppercase() {
2
} else {
0
};
ANSWERS[is_questioning + is_yelling]
}
In this approach you define an array that contains Bob’s answers, and each condition is given a score.
The correct answer is selected from the array by using the score as the array index.
The array is defined using the static lifetime annotation for a reference to an array of str references.
static means that the value will live for the life of the program.
The static annotation can be removed, as described in the Shortening section.
The reference symbols & are needed because the compiler must know the size of each binding.
The compiler doesn’t know the size of each str, but it does know the size of a reference to a str,
which is the size of a pointer for that platform.
For a 64-bit system, the size of a pointer is 64 bits.
So, each str is defined as a str reference (&str).
The array itself is defined as a reference, since the compiler does not know the size of the whole array and its contents.
-
The str method trim_end is used to remove whitespace from the end of the input.
-
If the trimmed input is_empty, then the response for nothing said is returned.
-
The ends_with method is used to determine if the input ends with a question mark.
If so, is_questioning is given the value 1, otherwise it is given the value 0.
-
The first half of the yell condition
message.chars().any(|ch| ch.is_alphabetic())
calls the str method chars to create an Iterator for the characters in the trimmed input.
- The iterated characters are passed into the
Iterator method any.
any passes each character to a closure using the char method is_alphabetic to ensure there is at least one letter character in the str.
This is because the second half of the condition tests that the uppercased input is the same as the input.
If the input were only "123" it would equal itself uppercased, but without letters it would not be a yell.
The uppercasing is done by using the str method to_uppercase.
If the input is a yell, then is_yelling is given the value 2, otherwise it is given the value 0.
The final expression returns the value in the ANSWERS array at the index of the combined values for is_questioning and is_yelling.
Note that the final line is just `ANSWERS[is_questioning + is_yelling]`
This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html)
from a function without using `return` and a semicolon.
| is_yelling | is_questioning | Index | Answer |
|---|
false | false | 0 + 0 = 0 | "Whatever." |
false | true | 0 + 1 = 1 | "Sure." |
true | false | 2 + 0 = 2 | "Whoa, chill out!" |
true | true | 2 + 1 = 3 | "Calm down, I know what I'm doing!" |
Shortening
For defining the array the static lifetime specifiers can be dropped, like so
const ANSWERS: &[&str] = &[
"Whatever.",
"Sure.",
"Whoa, chill out!",
"Calm down, I know what I'm doing!",
];
A lifetime specifier doesn’t change the actual lifetime of a binding.
It just makes the lifetime explicit.
When the compiler can figure out the lifetime, then the annotation can be dropped, which is called lifetime elision.
Source: Exercism rust/bob