[Darts] : Add Approaches (#3386)
* 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.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
50
exercises/practice/darts/.approaches/config.json
Normal file
50
exercises/practice/darts/.approaches/config.json
Normal file
@@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
146
exercises/practice/darts/.approaches/introduction.md
Normal file
146
exercises/practice/darts/.approaches/introduction.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Introduction
|
||||
|
||||
|
||||
There are multiple Pythonic ways to solve the Darts exercise.
|
||||
Among them are:
|
||||
|
||||
- Using `if-statements`
|
||||
- Using a `tuple` (or `list` or `dict`) and a `for-loop`
|
||||
- Using a `dict` (or `tuple` or `list`) and a `generator-expression`
|
||||
- Using `boolean` values as `ints`
|
||||
- Using a `dict` and `dict.get()`
|
||||
- Using `match/case` (_Python 3.10+ only_)
|
||||
|
||||
<br>
|
||||
|
||||
## General guidance
|
||||
|
||||
The goal of the Darts exercise is to score a single throw in a Darts game.
|
||||
The scoring areas are _concentric circles_, so boundary values need to be checked in order to properly score a throw.
|
||||
The key is to determine how far from the center the dart lands (_by calculating sqrt(x**2 + y**2), or a variation_) and then determine what scoring ring it falls into.
|
||||
|
||||
|
||||
**_Order matters_** - each bigger target circle contains all the smaller circles, so the most straightforward solution is to check the smallest circle first.
|
||||
Otherwise, you must box your scoring by checking both a _lower bound_ and an _upper bound_.
|
||||
|
||||
|
||||
Darts that fall on a _boundary_ are scored based on the area below the line (_closer to center_), so checking `<=` or `>=` is advised.
|
||||
|
||||
|
||||
## Approach: Using `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.
|
||||
For more details, see the [if statements][approach-if-statements] approach.
|
||||
|
||||
|
||||
## Approach: Using a `tuple` and a `loop`
|
||||
|
||||
```python
|
||||
def score(x_coord, y_coord):
|
||||
throw = x_coord**2 + y_coord**2
|
||||
rules = (1, 10), (25, 5), (100, 1), (200, 0)
|
||||
|
||||
for distance, points in rules:
|
||||
if throw <= distance:
|
||||
return points
|
||||
```
|
||||
|
||||
|
||||
This approach uses a loop to iterate through the _rules_ `tuple`, unpacking each `distance` and corresponding`score`.
|
||||
For more details, see the [tuple and loop][approach-tuple-and-loop] approach.
|
||||
|
||||
|
||||
## Approach: Using a `dict` with 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].
|
||||
For more information, see the [dict with generator-expression][approach-dict-with-generator-expression] approach.
|
||||
|
||||
|
||||
## Approach: 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
|
||||
```
|
||||
|
||||
|
||||
This approach exploits the fact that Boolean values are an integer subtype in Python.
|
||||
For more information, see the [boolean values as integers][approach-boolean-values-as-integers] approach.
|
||||
|
||||
|
||||
## Approach: 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)
|
||||
```
|
||||
|
||||
This approach uses a dictionary to hold the distance --> scoring mappings and `dict.get()` to retrieve the correct points value.
|
||||
For more details, read the [`Dictionary and dict.get()`][approach-dict-and-dict-get] approach.
|
||||
|
||||
|
||||
## Approach: Using `match/case` (structural pattern matching)
|
||||
|
||||
```python
|
||||
from math import hypot, ceil
|
||||
|
||||
|
||||
def score(x, y):
|
||||
match ceil(hypot(x, y)):
|
||||
case 0 | 1: return 10
|
||||
case 2 | 3 | 4 | 5: return 5
|
||||
case 6 | 7 | 8 | 9 | 10: return 1
|
||||
case _: return 0
|
||||
```
|
||||
|
||||
|
||||
This approach uses `Python 3.10`'s structural pattern matching with `return` values on the same line as `case`.
|
||||
A fallthrough case (`_`) is used if the dart throw is outside the outer circle of the target (_greater than 10_).
|
||||
For more details, see the [structural pattern matching][approach-struct-pattern-matching] approach.
|
||||
|
||||
|
||||
## Which approach to use?
|
||||
|
||||
Many of these approaches are a matter of personal preference - there are not significant memory or performance differences.
|
||||
Although a strong argument could be made for simplicity and clarity — many listed solutions (_while interesting_) are harder to reason about or are over-engineered for the current scope of the exercise.
|
||||
|
||||
[approach-boolean-values-as-integers]: https://exercism.org/tracks/python/exercises/darts/approaches/boolean-values-as-integers
|
||||
[approach-dict-and-dict-get]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-and-dict-get
|
||||
[approach-dict-with-generator-expression]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-with-gnerator-expresson
|
||||
[approach-if-statements ]: https://exercism.org/tracks/python/exercises/darts/approaches/if-statements
|
||||
[approach-struct-pattern-matching]: https://exercism.org/tracks/python/exercises/darts/approaches/struct-pattern-matching
|
||||
[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
|
||||
85
exercises/practice/darts/.approaches/match-case/content.md
Normal file
85
exercises/practice/darts/.approaches/match-case/content.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Use `match/case` (Structural Pattern Matching)
|
||||
|
||||
|
||||
```python
|
||||
from math import hypot, ceil
|
||||
|
||||
|
||||
def score(x, y):
|
||||
throw = ceil(hypot(x, y))
|
||||
|
||||
match throw:
|
||||
case 0 | 1: return 10
|
||||
case 2 | 3 | 4 | 5: return 5
|
||||
case 6 | 7 | 8 | 9 | 10: return 1
|
||||
case _: return 0
|
||||
|
||||
#OR#
|
||||
|
||||
def score(x, y):
|
||||
match ceil(hypot(x, y)):
|
||||
case 0 | 1: return 10
|
||||
case 2 | 3 | 4 | 5: return 5
|
||||
case 6 | 7 | 8 | 9 | 10: return 1
|
||||
case _: return 0
|
||||
```
|
||||
|
||||
This approach uses `Python 3.10`'s [`structural pattern matching`][structural-pattern-matching] with `return` values on the same line as `case`.
|
||||
Because the match is numeric, each case explicitly lists allowed values using the `|` (OR) operator.
|
||||
A fallthrough case (`_`) is used if the dart throw is greater than 10 (_the outer circle radius of the target_).
|
||||
This is equivalent to using `if-statements` to check throw values although some might argue it is clearer to read.
|
||||
An `if-statement` equivalent would be:
|
||||
|
||||
```python
|
||||
from math import hypot, ceil
|
||||
|
||||
|
||||
def score(x, y):
|
||||
throw = ceil(hypot(x, y))
|
||||
|
||||
if throw in (0, 1): return 10
|
||||
if throw in (2, 3, 4, 5): return 5
|
||||
if throw in (6, 7, 8, 9, 10): return 1
|
||||
|
||||
return 0
|
||||
```
|
||||
|
||||
One can also use `<`, `>`, or `<=` and `>=` in structural pattern matching, although the syntax becomes almost identical to using them with `if-statements`, but more verbose:
|
||||
|
||||
|
||||
```python
|
||||
from math import hypot, ceil
|
||||
|
||||
|
||||
def score(x, y):
|
||||
throw = ceil(hypot(x, y))
|
||||
|
||||
match throw:
|
||||
case throw if throw <= 1: return 10
|
||||
case throw if throw <= 5: return 5
|
||||
case throw if throw <= 10: return 1
|
||||
case _: return 0
|
||||
```
|
||||
|
||||
|
||||
Finally, one can use an [assignment expression][assignment-expression] or [walrus operator][walrus] to calculate the throw value rather than calculating and assigning a variable on a separate line.
|
||||
This isn't necessary (_the first variations shows this clearly_) and might be harder to reason about/understand for some programmers:
|
||||
|
||||
|
||||
```python
|
||||
from math import hypot, ceil
|
||||
|
||||
def score(x, y):
|
||||
match throw := ceil(hypot(x, y)):
|
||||
case throw if throw <= 1: return 10
|
||||
case throw if throw <=5: return 5
|
||||
case throw if throw <=10: return 1
|
||||
case _: return 0
|
||||
```
|
||||
|
||||
Using structural pattern matching for this exercise doesn't offer any clear performance advantages over the `if-statement`, but might be "cleaner", more "organized looking", or easier for others to scan/read.
|
||||
|
||||
|
||||
[assignment-expression]: https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-assignment_expression
|
||||
[structural-pattern-matching]: https://peps.python.org/pep-0636/
|
||||
[walrus]: https://peps.python.org/pep-0572/
|
||||
@@ -0,0 +1,8 @@
|
||||
from math import hypot, ceil
|
||||
|
||||
def score(x, y):
|
||||
match ceil(hypot(x, y)):
|
||||
case 0 | 1: return 10
|
||||
case 2 | 3 | 4 | 5: return 5
|
||||
case 6 | 7 | 8 | 9 | 10: return 1
|
||||
case _: return 0
|
||||
@@ -0,0 +1,55 @@
|
||||
# Use a tuple with a loop
|
||||
|
||||
```python
|
||||
def score(x_coord, y_coord):
|
||||
throw = x_coord**2 + y_coord**2
|
||||
rules = (1, 10), (25, 5), (100, 1), (200, 0)
|
||||
|
||||
for distance, points in rules:
|
||||
if throw <= distance:
|
||||
return points
|
||||
```
|
||||
|
||||
This approach uses a loop to iterate through the _rules_ `tuple`, unpacking each (`distance`, `points`) pair (_For a little more on unpacking, see [Tuple Unpacking Improves Python Code Readability][tuple-unpacking]_).
|
||||
If the calculated distance of the throw is less than or equal to a given distance, the score for that region is returned.
|
||||
A `list` of `lists`, a `list` of `tuples`, or a dictionary could be used here to the same effect:
|
||||
|
||||
```python
|
||||
def score(x_coord, y_coord):
|
||||
throw = x_coord**2 + y_coord**2
|
||||
rules = [[1, 10], [25, 5], [100, 1]]
|
||||
|
||||
for distance, points in rules:
|
||||
if throw <= distance:
|
||||
return points
|
||||
|
||||
return 0
|
||||
|
||||
#OR#
|
||||
|
||||
def score(x_coord, y_coord):
|
||||
throw = x_coord**2 + y_coord**2
|
||||
rules = [(1, 10), (25, 5), (100, 1), (200, 0)]
|
||||
|
||||
for distance, points in rules:
|
||||
if throw <= distance:
|
||||
return points
|
||||
|
||||
#OR#
|
||||
|
||||
def score(x_coord, y_coord):
|
||||
throw = x_coord**2 + y_coord**2
|
||||
rules = {1: 10, 25: 5, 100: 1}
|
||||
|
||||
for distance, points in rules.items():
|
||||
if throw <= distance:
|
||||
return points
|
||||
|
||||
return 0
|
||||
```
|
||||
|
||||
This approach would work nicely in a scenario where you expect to be adding more scoring "rings", since it is cleaner to edit the data structure than to add additional `if-statements` as you would have to in the [`if-statement` approach][approach-if-statements ].
|
||||
For the three rings as defined by the current exercise, it is a bit over-engineered to use a data structure + `loop`, and results in a slight (_**very** slight_) slowdown over using `if-statements`.
|
||||
|
||||
[tuple-unpacking]: https://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/#Unpacking_in_a_for_loop
|
||||
[approach-if-statements ]: https://exercism.org/tracks/python/exercises/darts/approaches/if-statements
|
||||
@@ -0,0 +1,7 @@
|
||||
def score(x_coord, y_coord):
|
||||
distance = x_coord**2 + y_coord**2
|
||||
rules = (1.0, 10), (25.0, 5), (100.0, 1), (200.0, 0)
|
||||
|
||||
for distance, point in rules:
|
||||
if length <= distance:
|
||||
return point
|
||||
Reference in New Issue
Block a user