Dig Deeper
Use If Statements
Use if-statements
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), (x2 +y2) can be calculated instead, and the scoring rings can be adjusted to 1, 25, and 100:
# 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
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:
# 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
Use a Tuple & Loop through Scores
Use a tuple with a loop
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).
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:
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.
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.
Use Structural Pattern Matching (‘Match-Case’)
Use match/case (Structural Pattern Matching)
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 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:
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:
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 or walrus operator 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:
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.
Source: Exercism python/darts