Skip to content

Commit ab76008

Browse files
authored
Add approaches to Bob (exercism#3200)
Add approaches to Bob
1 parent c381955 commit ab76008

File tree

12 files changed

+417
-0
lines changed

12 files changed

+417
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Answer list
2+
3+
```python
4+
_ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!',
5+
"Calm down, I know what I'm doing!"]
6+
7+
8+
def response(hey_bob):
9+
hey_bob = hey_bob.rstrip()
10+
if not hey_bob:
11+
return 'Fine. Be that way!'
12+
isShout = (2 if hey_bob.isupper() else 0)
13+
isQuestion = (1 if hey_bob.endswith('?') else 0)
14+
return _ANSWERS[isShout + isQuestion]
15+
16+
```
17+
18+
In this approach you define a [list][list] that contains Bob’s answers, and each condition is given a score.
19+
The correct answer is selected from the list by using the score as the list index.
20+
21+
Python doesn't _enforce_ having real constant values,
22+
but the `_ANSWERS` list is defined with all uppercase letters, which is the naming convention for a Python [constant][const].
23+
It indicates that the value is not intended to be changed.
24+
Python also does not have real [private][private] variables,
25+
but a leading underscore is the naming convention for indicating that a variable is not meant to be part of the public API.
26+
It should be considered an implementation detail and subject to change without notice.
27+
28+
The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input.
29+
If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing.
30+
Since it doesn't matter if there is leading whitespace, the `rstrip` function is used instead of [`strip`][strip].
31+
32+
A [ternary operator][ternary] is used for determining the score for a shout and a question.
33+
34+
The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase.
35+
36+
```exercism/note
37+
A cased character is one which differs between lowercase and uppercase.
38+
For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase.
39+
`a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase.
40+
```
41+
42+
If `isupper` is `True`, then `isShout` is given the value of `2`; otherwise, it is given the value of `0`.
43+
44+
The [`endswith`][endswith] method is used to determine if the input ends with a question mark.
45+
If the test for `endswith('?')` is `True`, then `isQuestion` is given the value of `1`; otherwise it is given the value of `0`.
46+
47+
48+
The response is selected from the list by the index like so
49+
50+
| isShout | isQuestion | Index | Answer |
51+
| ------- | ---------- | --------- | ------------------------------------- |
52+
| `false` | `false` | 0 + 0 = 0 | `"Whatever."` |
53+
| `false` | `true` | 0 + 1 = 1 | `"Sure."` |
54+
| `true` | `false` | 2 + 0 = 2 | `"Whoa, chill out!"` |
55+
| `true` | `true` | 2 + 1 = 3 | `"Calm down, I know what I'm doing!"` |
56+
57+
58+
[list]: https://docs.python.org/3/library/stdtypes.html?highlight=list#list
59+
[const]: https://realpython.com/python-constants/
60+
[private]: https://docs.python.org/3/tutorial/classes.html#private-variables
61+
[rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip
62+
[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/
63+
[not]: https://docs.python.org/3/reference/expressions.html#not
64+
[strip]: https://docs.python.org/3/library/stdtypes.html?highlight=strip#str.strip
65+
[ternary]: https://www.pythontutorial.net/python-basics/python-ternary-operator/
66+
[isupper]: https://docs.python.org/3/library/stdtypes.html?highlight=isupper#str.isupper
67+
[endswith]: https://docs.python.org/3/library/stdtypes.html?highlight=endswith#str.endswith
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
_ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!',
2+
"Calm down, I know what I'm doing!"]
3+
# code snipped
4+
isShout = (2 if hey_bob.isupper() else 0)
5+
isQuestion = (1 if hey_bob.endswith('?') else 0)
6+
return _ANSWERS[isShout + isQuestion]
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": "972b7546-67ca-4364-9367-02d68a8c0314",
9+
"slug": "if-statements",
10+
"title": "If statements",
11+
"blurb": "Use if statements to return the answer.",
12+
"authors": ["bobahop"]
13+
},
14+
{
15+
"uuid": "138fcf61-262c-47f6-aac3-96528b24ee6b",
16+
"slug": "if-statements-nested",
17+
"title": "if statements nested",
18+
"blurb": "Use nested if statements to return the answer.",
19+
"authors": ["bobahop"]
20+
},
21+
{
22+
"uuid": "d74a1d7d-af85-417c-8deb-164536e4ab1b",
23+
"slug": "answer-list",
24+
"title": "Answer list",
25+
"blurb": "Index into a list to return the answer.",
26+
"authors": ["bobahop"]
27+
}
28+
]
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# `if` statements nested
2+
3+
```python
4+
def response(hey_bob):
5+
hey_bob = hey_bob.rstrip()
6+
if not hey_bob:
7+
return 'Fine. Be that way!'
8+
isShout = hey_bob.isupper()
9+
isQuestion = hey_bob.endswith('?')
10+
if isShout:
11+
if isQuestion:
12+
return "Calm down, I know what I'm doing!"
13+
else:
14+
return 'Whoa, chill out!'
15+
if isQuestion:
16+
return 'Sure.'
17+
return 'Whatever.'
18+
19+
```
20+
21+
In this approach you have a series of `if` statements using the calculated variables to evaluate the conditions, some of which are nested.
22+
As soon as a `True` condition is found, the correct response is returned.
23+
24+
The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input.
25+
If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing.
26+
Since it doesn't matter if there is leading whitespace, the `rstrip` function is used instead of [`strip`][strip].
27+
28+
The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase.
29+
30+
```exercism/note
31+
A cased character is one which differs between lowercase and uppercase.
32+
For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase.
33+
`a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase.
34+
```
35+
36+
The [`endswith`][endswith] method is used to determine if the input ends with a question mark.
37+
38+
Instead of testing a shout and a question on the same line, this approach first tests if the input is a shout.
39+
If it is a shout, then the nested `if`/[`else`][else] statement returns if it is a shouted question or just a shout.
40+
If it is not a shout, then the flow of execution skips down to the non-nested test for if the input is a question.
41+
42+
[rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip
43+
[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/
44+
[not]: https://docs.python.org/3/reference/expressions.html#not
45+
[strip]: https://docs.python.org/3/library/stdtypes.html?highlight=strip#str.strip
46+
[isupper]: https://docs.python.org/3/library/stdtypes.html?highlight=isupper#str.isupper
47+
[endswith]: https://docs.python.org/3/library/stdtypes.html?highlight=endswith#str.endswith
48+
[else]: https://docs.python.org/3/reference/compound_stmts.html#else
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
if isShout:
2+
if isQuestion:
3+
return "Calm down, I know what I'm doing!"
4+
else:
5+
return 'Whoa, chill out!'
6+
if isQuestion:
7+
return 'Sure.'
8+
return 'Whatever.'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# `if` statements
2+
3+
```python
4+
def response(hey_bob):
5+
hey_bob = hey_bob.rstrip()
6+
if not hey_bob:
7+
return 'Fine. Be that way!'
8+
isShout = hey_bob.isupper()
9+
isQuestion = hey_bob.endswith('?')
10+
if isShout and isQuestion:
11+
return "Calm down, I know what I'm doing!"
12+
if isShout:
13+
return 'Whoa, chill out!'
14+
if isQuestion:
15+
return 'Sure.'
16+
return 'Whatever.'
17+
18+
```
19+
20+
In this approach you have a series of `if` statements using the calculated variables to evaluate the conditions.
21+
As soon as a `True` condition is found, the correct response is returned.
22+
23+
```exercism/note
24+
Note that there are no `elif` or `else` statements.
25+
If an `if` statement can return, then an `elif` or `else` is not needed.
26+
Execution will either return or will continue to the next statement anyway.
27+
```
28+
29+
The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input.
30+
If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing.
31+
Since it doesn't matter if there is leading whitespace, the `rstrip` function is used instead of [`strip`][strip].
32+
33+
The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase.
34+
35+
```exercism/note
36+
A cased character is one which differs between lowercase and uppercase.
37+
For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase.
38+
`a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase.
39+
```
40+
41+
The [`endswith`][endswith] method is used to determine if the input ends with a question mark.
42+
43+
The [logical AND operator][and] (`and`) is used to test a shout and a question together.
44+
45+
[rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip
46+
[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/
47+
[not]: https://docs.python.org/3/reference/expressions.html#not
48+
[strip]: https://docs.python.org/3/library/stdtypes.html?highlight=strip#str.strip
49+
[isupper]: https://docs.python.org/3/library/stdtypes.html?highlight=isupper#str.isupper
50+
[endswith]: https://docs.python.org/3/library/stdtypes.html?highlight=endswith#str.endswith
51+
[and]: https://realpython.com/python-and-operator/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
if isShout and isQuestion:
2+
return "Calm down, I know what I'm doing!"
3+
if isShout:
4+
return 'Whoa, chill out!'
5+
if isQuestion:
6+
return 'Sure.'
7+
return 'Whatever.'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
The `if` statements could also be nested.
6+
A list 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 stripped, [`rstrip`][rstrip] only once.
13+
14+
- Use the [`endswith`][endswith] 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 `elif` 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+
```python
28+
def response(hey_bob):
29+
hey_bob = hey_bob.rstrip()
30+
if not hey_bob:
31+
return 'Fine. Be that way!'
32+
isShout = hey_bob.isupper()
33+
isQuestion = hey_bob.endswith('?')
34+
if isShout and isQuestion:
35+
return "Calm down, I know what I'm doing!"
36+
if isShout:
37+
return 'Whoa, chill out!'
38+
if isQuestion:
39+
return 'Sure.'
40+
return 'Whatever.'
41+
42+
```
43+
44+
For more information, check the [`if` statements approach][approach-if].
45+
46+
## Approach: `if` statements nested
47+
48+
```python
49+
def response(hey_bob):
50+
hey_bob = hey_bob.rstrip()
51+
if not hey_bob:
52+
return 'Fine. Be that way!'
53+
isShout = hey_bob.isupper()
54+
isQuestion = hey_bob.endswith('?')
55+
if isShout:
56+
if isQuestion:
57+
return "Calm down, I know what I'm doing!"
58+
else:
59+
return 'Whoa, chill out!'
60+
if isQuestion:
61+
return 'Sure.'
62+
return 'Whatever.'
63+
64+
```
65+
66+
For more information, check the [`if` statements nested approach][approach-if-nested].
67+
68+
## Other approaches
69+
70+
Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows:
71+
72+
### Other approach: answer list
73+
74+
A list can be defined that contains Bob’s answers, and each condition is given a score.
75+
The correct answer is selected from the list by using the score as the list index.
76+
For more information, check the [answer list approach][approach-answer-list].
77+
78+
## Which approach to use?
79+
80+
The nested `if` approach is fastest, but some may consider it a bit less readable than the unnested `if` statements.
81+
The answer list approach is slowest, but some may prefer doing away with the chain of `if` statements.
82+
Since the difference between them is a few nanoseconds, which one is used may be a matter of stylistic preference.
83+
84+
- To compare the performance of the approaches, check out the [Performance article][article-performance].
85+
86+
[rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip
87+
[endswith]: https://docs.python.org/3/library/stdtypes.html?highlight=strip#str.endswith
88+
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
89+
[approach-if]: https://exercism.org/tracks/python/exercises/bob/approaches/if-statements
90+
[approach-if-nested]: https://exercism.org/tracks/python/exercises/bob/approaches/if-statements-nested
91+
[approach-answer-list]: https://exercism.org/tracks/python/exercises/bob/approaches/answer-list
92+
[article-performance]: https://exercism.org/tracks/python/exercises/bob/articles/performance
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"articles": [
3+
{
4+
"uuid": "7b04a95b-d56f-48d3-bb79-3523cfee44ad",
5+
"slug": "performance",
6+
"title": "Performance deep dive",
7+
"blurb": "Deep dive to find out the most performant approach for Bob's response.",
8+
"authors": ["bobahop"]
9+
}
10+
]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import timeit
2+
3+
loops = 1_000_000
4+
5+
val = timeit.timeit("""response("I really don't have anything to say.")""",
6+
"""
7+
def response(hey_bob):
8+
hey_bob = hey_bob.rstrip()
9+
if not hey_bob:
10+
return 'Fine. Be that way!'
11+
isShout = hey_bob.isupper()
12+
isQuestion = hey_bob.endswith('?')
13+
if isShout and isQuestion:
14+
return "Calm down, I know what I'm doing!"
15+
if isShout:
16+
return 'Whoa, chill out!'
17+
if isQuestion:
18+
return 'Sure.'
19+
return 'Whatever.'
20+
21+
""", number=loops) / loops
22+
23+
print(f"if statements: {val}")
24+
25+
26+
val = timeit.timeit("""response("I really don't have anything to say.")""",
27+
"""
28+
def response(hey_bob):
29+
hey_bob = hey_bob.rstrip()
30+
if not hey_bob:
31+
return 'Fine. Be that way!'
32+
isShout = hey_bob.isupper()
33+
isQuestion = hey_bob.endswith('?')
34+
if isShout:
35+
if isQuestion:
36+
return "Calm down, I know what I'm doing!"
37+
else:
38+
return 'Whoa, chill out!'
39+
if isQuestion:
40+
return 'Sure.'
41+
return 'Whatever.'
42+
43+
""", number=loops) / loops
44+
45+
print(f"if statements nested: {val}")
46+
47+
val = timeit.timeit("""response("I really don't have anything to say.")""",
48+
"""
49+
50+
_ANSWERS = ["Whatever.", "Sure.", "Whoa, chill out!", "Calm down, I know what I'm doing!"]
51+
52+
def response(hey_bob):
53+
hey_bob = hey_bob.rstrip()
54+
if not hey_bob: return 'Fine. Be that way!'
55+
isShout = 2 if hey_bob.isupper() else 0
56+
isQuestion = 1 if hey_bob.endswith('?') else 0
57+
return _ANSWERS[isShout + isQuestion]
58+
59+
60+
""", number=loops) / loops
61+
62+
print(f"answer list: {val}")

0 commit comments

Comments
 (0)