-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Initial docs for darts approaches. * Initial rough draft of darts approaches. * Removing .articles for now. * Wrapped up approaches content details and removed performance info. * Cleaned up typos and changed toss to throw.
- Loading branch information
Showing
14 changed files
with
625 additions
and
0 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
exercises/practice/darts/.approaches/booleans-as-ints/content.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Using Boolean Values as Integers | ||
|
||
|
||
```python | ||
def score(x_coord, y_coord): | ||
radius = (x_coord**2 + y_coord**2) | ||
return (radius<=1)*5 + (radius<=25)*4 + (radius<=100)*1 | ||
``` | ||
|
||
|
||
In Python, the [Boolean values `True` and `False` are _subclasses_ of `int`][bools-as-ints] and can be interpreted as `0` (False) and `1` (True) in a mathematical context. | ||
This approach leverages that interpretation by checking which areas the throw falls into and multiplying each Boolean `int` by a scoring multiple. | ||
For example, a throw that lands on the 25 (_or 5 if using `math.sqrt(x**2 + y**2)`_) circle should have a score of 5: | ||
|
||
```python | ||
>>> (False)*5 + (True)*4 + (True)*1 | ||
5 | ||
``` | ||
|
||
|
||
This makes for very compact code and has the added boost of not requiring any `loops` or additional data structures. | ||
However, it is considered bad form to rely on Boolean interpretation. | ||
Instead, the Python documentation recommends an explicit conversion to `int`: | ||
|
||
|
||
```python | ||
def score(x_coord, y_coord): | ||
radius = (x_coord**2 + y_coord**2) | ||
return int(radius<=1)*5 + int(radius<=25)*4 + int(radius<=100)*1 | ||
``` | ||
|
||
Beyond that recommendation, the terseness of this approach might be harder to reason about or decode — especially if a programmer is coming from a programming langauge that does not treat Boolean values as `ints`. | ||
Despite the "radius" variable name, it is also more difficult to relate the scoring "rings" of the Dartboard to the values being checked and calculated in the `return` statement. | ||
If using this code in a larger program, it would be strongly recommended that a docstring be provided to explain the Dartboard rings, scoring rules, and the corresponding scores. | ||
|
||
[bools-as-ints]: https://docs.python.org/3/library/stdtypes.html#boolean-type-bool |
3 changes: 3 additions & 0 deletions
3
exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
def score(x_coord, y_coord): | ||
radius = (x_coord**2 + y_coord**2) | ||
return (radius<=1)*5 + (radius<=25)*4 +(radius<=100)*1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
{ | ||
"introduction": { | ||
"authors": ["bethanyg"], | ||
"contributors": [] | ||
}, | ||
"approaches": [ | ||
{ | ||
"uuid": "7d78f598-8b4c-4f7f-89e1-e8644e934a4c", | ||
"slug": "if-statements", | ||
"title": "Use If Statements", | ||
"blurb": "Use if-statements to check scoring boundaries for a dart throw.", | ||
"authors": ["bethanyg"] | ||
}, | ||
{ | ||
"uuid": "f8f5533a-09d2-4b7b-9dec-90f268bfc03b", | ||
"slug": "tuple-and-loop", | ||
"title": "Use a Tuple & Loop through Scores", | ||
"blurb": "Score the Dart throw by looping through a tuple of scores.", | ||
"authors": ["bethanyg"] | ||
}, | ||
{ | ||
"uuid": "a324f99e-15bb-43e0-9181-c1652094bc4f", | ||
"slug": "match-case", | ||
"title": "Use Structural Pattern Matching ('Match-Case')", | ||
"blurb": "Use a Match-Case (Structural Pattern Matching) to score the dart throw.)", | ||
"authors": ["bethanyg"] | ||
}, | ||
{ | ||
"uuid": "966bd2dd-c4fd-430b-ad77-3a304dedd82e", | ||
"slug": "dict-and-generator", | ||
"title": "Use a Dictionary with a Generator Expression", | ||
"blurb": "Use a generator expression looping over a scoring dictionary, getting the max score for the dart throw.", | ||
"authors": ["bethanyg"] | ||
}, | ||
{ | ||
"uuid": "5b087f50-31c5-4b84-9116-baafd3a30ed6", | ||
"slug": "booleans-as-ints", | ||
"title": "Use Boolean Values as Integers", | ||
"blurb": "Use True and False as integer values to calculate the score of the dart throw.", | ||
"authors": ["bethanyg"] | ||
}, | ||
{ | ||
"uuid": "0b2dbcd3-f0ac-45f7-af75-3451751fd21f", | ||
"slug": "dict-and-dict-get", | ||
"title": "Use a Dictionary with dict.get", | ||
"blurb": "Loop over a dictionary and retrieve score via dct.get.", | ||
"authors": ["bethanyg"] | ||
} | ||
] | ||
} |
72 changes: 72 additions & 0 deletions
72
exercises/practice/darts/.approaches/dict-and-dict-get/content.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Using a Dictionary and `dict.get()` | ||
|
||
|
||
```python | ||
def score(x_coord, y_coord): | ||
point = (x_coord**2 + y_coord**2) | ||
scores = { | ||
point <= 100: 1, | ||
point <= 25: 5, | ||
point <= 1: 10 | ||
} | ||
|
||
return scores.get(True, 0) | ||
``` | ||
|
||
At first glance, this approach looks similar to the [Booleans as Integers][approach-boolean-values-as-integers] approach, due to the Boolean evaluation used in the dictionary keys. | ||
However, this approach is **not** interpreting Booleans as integers and is instead exploiting three key properties of [dictionaries][dicts]: | ||
|
||
|
||
1. [Keys must be hashable][hashable-keys] — in other words, keys have to be _unique_. | ||
2. Insertion order is preserved (_as of `Python 3.7`_), and evaluation/iteration happens in insertion order. | ||
3. Duplicate keys _overwrite_ existing keys. | ||
If the first key is `True` and the third key is `True`, the _value_ from the third key will overwrite the value from the first key. | ||
|
||
Finally, the `return` line uses [`dict.get()`][dict-get] to `return` a default value of 0 when a throw is outside the existing circle radii. | ||
To see this in action, you can view this code on [Python Tutor][dict-get-python-tutor]. | ||
|
||
|
||
Because of the listed dictionary qualities, **_order matters_**. | ||
This approach depends on the outermost scoring circle containing all smaller circles and that | ||
checks proceed from largest --> smallest circle. | ||
Iterating in the opposite direction will not resolve to the correct score. | ||
The following code variations do not pass the exercise tests: | ||
|
||
|
||
```python | ||
|
||
def score(x_coord, y_coord): | ||
point = (x_coord**2 + y_coord**2) | ||
scores = { | ||
point <= 1: 10, | ||
point <= 25: 5, | ||
point <= 100: 1, | ||
} | ||
|
||
return scores.get(True, 0) | ||
|
||
#OR# | ||
|
||
def score(x_coord, y_coord): | ||
point = (x_coord**2 + y_coord**2) | ||
scores = { | ||
point <= 25: 5, | ||
point <= 1: 10, | ||
point <= 100: 1, | ||
} | ||
|
||
return scores.get(True, 0) | ||
|
||
``` | ||
|
||
While this approach is a _very clever_ use of dictionary properties, it is likely to be very hard to reason about for those who are not deeply knowledgeable. | ||
Even those experienced in Python might take longer than usual to figure out what is happening in the code. | ||
Extensibility could also be error-prone due to needing a strict order for the `dict` keys. | ||
|
||
This approach offers no space or speed advantages over using `if-statements` or other strategies, so is not recommended for use beyond a learning context. | ||
|
||
[approach-boolean-values-as-integers]: https://exercism.org/tracks/python/exercises/darts/approaches/boolean-values-as-integers | ||
[dicts]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict | ||
[dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get | ||
[dict-get-python-tutor]: https://pythontutor.com/render.html#code=def%20score%28x_coord,%20y_coord%29%3A%0A%20%20%20%20point%20%3D%20%28x_coord**2%20%2B%20y_coord**2%29%0A%20%20%20%20scores%20%3D%20%7B%0A%20%20%20%20%20%20%20%20point%20%3C%3D%20100%3A%201,%0A%20%20%20%20%20%20%20%20point%20%3C%3D%2025%3A%205,%0A%20%20%20%20%20%20%20%20point%20%3C%3D%201%3A%2010%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20return%20scores.get%28True,%200%29%0A%20%20%20%20%0Aprint%28score%281,3%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false | ||
[hashable-keys]: https://www.pythonmorsels.com/what-are-hashable-objects/#dictionary-keys-must-be-hashable |
5 changes: 5 additions & 0 deletions
5
exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
def score(x_coord, y_coord): | ||
point = (x_coord**2 + y_coord**2) | ||
scores = {point <= 100: 1, point <= 25: 5, point <= 1: 10} | ||
|
||
return scores.get(True, 0) |
69 changes: 69 additions & 0 deletions
69
exercises/practice/darts/.approaches/dict-and-generator/content.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Use a Dictionary and a Generator Expression | ||
|
||
```python | ||
def score(x_coord, y_coord): | ||
throw = x_coord**2 + y_coord**2 | ||
rules = {1: 10, 25: 5, 100: 1, 200: 0} | ||
|
||
return max(point for distance, point in | ||
rules.items() if throw <= distance) | ||
``` | ||
|
||
|
||
This approach is very similar to the [tuple and loop][approach-tuple-and-loop] approach, but iterates over [`dict.items()`][dict-items] and writes the `loop` as a [`generator-expression`][generator-expression] inside `max()`. | ||
In cases where the scoring circles overlap, `max()` will return the maximum score available for the throw. | ||
The generator expression inside `max()` is the equivalent of using a `for-loop` and a variable to determine the max score: | ||
|
||
|
||
```python | ||
def score(x_coord, y_coord): | ||
throw = x_coord**2 + y_coord**2 | ||
rules = {1: 10, 25: 5, 100: 1} | ||
max_score = 0 | ||
|
||
for distance, point in rules.items(): | ||
if throw <= distance and point > max_score: | ||
max_score = point | ||
return max_score | ||
``` | ||
|
||
|
||
A `list` or `tuple` can also be used in place of `max()`, but then requires an index to return the max score: | ||
|
||
```python | ||
def score(x_coord, y_coord): | ||
throw = x_coord**2 + y_coord**2 | ||
rules = {1: 10, 25: 5, 100: 1, 200: 0} | ||
|
||
return [point for distance, point in | ||
rules.items() if throw <= distance][0] #<-- have to specify index 0. | ||
|
||
#OR# | ||
|
||
def score(x_coord, y_coord): | ||
throw = x_coord**2 + y_coord**2 | ||
rules = {1: 10, 25: 5, 100: 1, 200: 0} | ||
|
||
return tuple(point for distance, point in | ||
rules.items() if throw <= distance)[0] | ||
``` | ||
|
||
|
||
This solution can even be reduced to a "one-liner". | ||
However, this is not performant, and is difficult to read: | ||
|
||
```python | ||
def score(x_coord, y_coord): | ||
return max(point for distance, point in | ||
{1: 10, 25: 5, 100: 1, 200: 0}.items() if | ||
(x_coord**2 + y_coord**2) <= distance) | ||
``` | ||
|
||
While all of these variations do pass the tests, they suffer from even more over-engineering/performance caution than the earlier tuple and loop approach (_although for the data in this problem, the performance hit is slight_). | ||
Additionally, the dictionary will take much more space in memory than using a `tuple` of tuples to hold scoring values. | ||
In some circumstances, these variations might also be harder to reason about for those not familiar with `generator-expressions` or `list comprehensions`. | ||
|
||
|
||
[approach-tuple-and-loop]: https://exercism.org/tracks/python/exercises/darts/approaches/tuple-and-loop | ||
[dict-items]: https://docs.python.org/3/library/stdtypes.html#dict.items | ||
[generator-expression]: https://dbader.org/blog/python-generator-expressions |
8 changes: 8 additions & 0 deletions
8
exercises/practice/darts/.approaches/dict-and-generator/snippet.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
def score(x_coord, y_coord): | ||
length = x_coord**2 + y_coord**2 | ||
rules = {1.0: 10, 25.0: 5, 100.0: 1, 200: 0} | ||
score = max(point for | ||
distance, point in | ||
rules.items() if length <= distance) | ||
|
||
return score |
73 changes: 73 additions & 0 deletions
73
exercises/practice/darts/.approaches/if-statements/content.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Use `if-statements` | ||
|
||
|
||
```python | ||
import math | ||
|
||
# Checks scores from the center --> edge. | ||
def score(x_coord, y_coord): | ||
distance = math.sqrt(x_coord**2 + y_coord**2) | ||
|
||
if distance <= 1: return 10 | ||
if distance <= 5: return 5 | ||
if distance <= 10: return 1 | ||
|
||
return 0 | ||
``` | ||
|
||
This approach uses [concept:python/conditionals]() to check the boundaries for each scoring ring, returning the corresponding score. | ||
Calculating the euclidian distance is assigned to the variable "distance" to avoid having to re-calculate it for every if check. | ||
Because the `if-statements` are simple and readable, they're written on one line to shorten the function body. | ||
Zero is returned if no other check is true. | ||
|
||
|
||
To avoid importing the `math` module (_for a very very slight speedup_), (x**2 +y**2) can be calculated instead, and the scoring rings can be adjusted to 1, 25, and 100: | ||
|
||
|
||
```python | ||
# Checks scores from the center --> edge. | ||
def score(x_coord, y_coord): | ||
distance = x_coord**2 + y_coord**2 | ||
|
||
if distance <= 1: return 10 | ||
if distance <= 25: return 5 | ||
if distance <= 100: return 1 | ||
|
||
return 0 | ||
``` | ||
|
||
|
||
# Variation 1: Check from Edge to Center Using Upper and Lower Bounds | ||
|
||
|
||
```python | ||
import math | ||
|
||
# Checks scores from the edge --> center | ||
def score(x_coord, y_coord): | ||
distance = math.sqrt(x_coord**2 + y_coord**2) | ||
|
||
if distance > 10: return 0 | ||
if 5 < distance <= 10: return 1 | ||
if 1 < distance <= 5: return 5 | ||
|
||
return 10 | ||
``` | ||
|
||
This variant checks from the edge moving inward, checking both a lower and upper bound due to the overlapping scoring circles in this direction. | ||
|
||
Scores for any of these solutions can also be assigned to a variable to avoid multiple `returns`, but this isn't really necessary: | ||
|
||
```python | ||
# Checks scores from the edge --> center | ||
def score(x_coord, y_coord): | ||
distance = x_coord**2 + y_coord**2 | ||
points = 10 | ||
|
||
if distance > 100: points = 0 | ||
if 25 < distance <= 100: points = 1 | ||
if 1 < distance <= 25: points = 5 | ||
|
||
return points | ||
``` | ||
|
8 changes: 8 additions & 0 deletions
8
exercises/practice/darts/.approaches/if-statements/snippet.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import math | ||
|
||
def score(x_coord, y_coord): | ||
distance = math.sqrt(x_coord**2 + y_coord**2) | ||
if distance <= 1: return 10 | ||
if distance <= 5: return 5 | ||
if distance <= 10: return 1 | ||
return 0 |
Oops, something went wrong.