Introduction
Each year, something new is “all the rage” in your high school.
This year it is a dice game: Yacht.
The game of Yacht is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor.
The game consists of twelve rounds.
In each, five dice are rolled and the player chooses one of twelve categories.
The chosen category is then used to score the throw of the dice.
Instructions
Instructions
Given five dice and a category, calculate the score of the dice for that category.
You'll always be presented with five dice.
Each dice's value will be between one and six inclusively.
The dice may be unordered.
Scores in Yacht
| Category | Score | Description | Example |
|---|
| Ones | 1 × number of ones | Any combination | 1 1 1 4 5 scores 3 |
| Twos | 2 × number of twos | Any combination | 2 2 3 4 5 scores 4 |
| Threes | 3 × number of threes | Any combination | 3 3 3 3 3 scores 15 |
| Fours | 4 × number of fours | Any combination | 1 2 3 3 5 scores 0 |
| Fives | 5 × number of fives | Any combination | 5 1 5 2 5 scores 15 |
| Sixes | 6 × number of sixes | Any combination | 2 3 4 5 6 scores 6 |
| Full House | Total of the dice | Three of one number and two of another | 3 3 3 5 5 scores 19 |
| Four of a Kind | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 |
| Little Straight | 30 points | 1-2-3-4-5 | 1 2 3 4 5 scores 30 |
| Big Straight | 30 points | 2-3-4-5-6 | 2 3 4 5 6 scores 30 |
| Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 |
| Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 |
If the dice do not satisfy the requirements of a category, the score is zero.
If, for example, Four Of A Kind is entered in the Yacht category, zero points are scored.
A Yacht scores zero if entered in the Full House category.
Dig Deeper
Lambdas with Functions
Approach: Using Lambdas with Functions
Each bit of functionality for each category can be encoded in an anonymous function (otherwise known as a lambda expression or lambda form), and the constant name set to that function.
In score, we call the category (as it now points to a function) passing in dice as an argument.
def digits(num):
return lambda dice: dice.count(num) * num
YACHT = lambda dice: 50 if len(set(dice)) == 1 else 0
ONES = digits(1)
TWOS = digits(2)
THREES = digits(3)
FOURS = digits(4)
FIVES = digits(5)
SIXES = digits(6)
FULL_HOUSE = lambda dice: sum(dice) if len(set(dice)) == 2 and dice.count(dice[0]) in [2, 3] else 0
FOUR_OF_A_KIND = lambda dice: 4 * dice[1] if dice[0] == dice[3] or dice[1] == dice[4] else 0
LITTLE_STRAIGHT = lambda dice: 30 if sorted(dice) == [1, 2, 3, 4, 5] else 0
BIG_STRAIGHT = lambda dice: 30 if sorted(dice) == [2, 3, 4, 5, 6] else 0
CHOICE = sum
def score(dice, category):
return category(dice)
Instead of setting each constant in ONES through SIXES to a separate function, we create a function digits that returns a function, using closures transparently.
For LITTLE_STRAIGHT and BIG_STRAIGHT, we first sort the dice and then check it against the hard-coded value.
Another way to solve this would be to check if sum(dice) == 20 and len(set(dice)) == 5 (15 in LITTLE_STRAIGHT).
In CHOICE, lambda number : sum(number) is shortened to just sum.
In FULL_HOUSE, we create a set to remove the duplicates and check the set’s length along with the individual counts.
For FOUR_OF_A_KIND, we check if the first and the fourth element are the same or the second and the last element are the same - if so, there are (at least) four of the same number in the array.
This solution is a succinct way to solve the exercise, although some of the one-liners can get a little long and hard to read.
Additionally, PEP8 does not recommend assigning constant or variable names to lambda expressions, so it is a better practice to use def:
def digits(num):
return lambda dice: dice.count(num) * num
def YACHT(dice): return 50 if len(set(dice)) == 1 else 0
ONES = digits(1)
TWOS = digits(2)
THREES = digits(3)
FOURS = digits(4)
FIVES = digits(5)
SIXES = digits(6)
def FULL_HOUSE(dice): return sum(dice) if len(set(dice)) == 2 and dice.count(dice[0]) in [2, 3] else 0
def FOUR_OF_A_KIND(dice): return 4 * sorted(dice)[1] if len(set(dice)) < 3 and dice.count(dice[0]) in (1, 4, 5) else 0
def LITTLE_STRAIGHT(dice): return 30 if sorted(dice) == [1, 2, 3, 4, 5] else 0
def BIG_STRAIGHT(dice): return 30 if sorted(dice) == [2, 3, 4, 5, 6] else 0
CHOICE = sum
def score(dice, category):
return category(dice)
As you can see from the examples, the ternary operator (or ternary form) is crucial in solving the exercise using one liners.
As functions are being used, it might be a better strategy to spread the code over multiple lines to improve readability.
def YACHT(dice):
if dice.count(dice[0]) == len(dice):
return 50
return 0
If structure
If structure
The constants here can be set to random, null, or numeric values, and an if structure inside the score function can determine the code to be executed.
As one-liners aren’t necessary here, we can spread out the code to make it look neater:
ONES = 1
TWOS = 2
THREES = 3
FOURS = 4
FIVES = 5
SIXES = 6
FULL_HOUSE = 'FULL_HOUSE'
FOUR_OF_A_KIND = 'FOUR_OF_A_KIND'
LITTLE_STRAIGHT = 'LITTLE_STRAIGHT'
BIG_STRAIGHT = 'BIG_STRAIGHT'
CHOICE = 'CHOICE'
YACHT = 'YACHT'
def score(dice, category):
if category in (1,2,3,4,5,6):
return dice.count(category) * category
elif category == 'FULL_HOUSE':
if len(set(dice)) == 2 and dice.count(dice[0]) in [2, 3]:
return sum(dice) or 0
elif category == 'FOUR_OF_A_KIND':
if dice[0] == dice[3] or dice[1] == dice[4]:
return dice[1] * 4 or 0
elif category == 'LITTLE_STRAIGHT':
if sorted(dice) == [1, 2, 3, 4, 5]:
return 30 or 0
elif category == 'BIG_STRAIGHT':
if sorted(dice) == [2, 3, 4, 5, 6]:
return 30 or 0
elif category == 'YACHT':
if all(num == dice[0] for num in dice):
return 50
elif category == 'CHOICE':
return sum(dice)
return 0
Note that the code inside the if statements themselves can differ, but the key idea here is to use if and elif to branch out the code, and return 0 at the end if nothing else has been returned.
The if condition itself can be different, with people commonly checking if category == ONES as opposed to category == 'ONES' (or whatever the dummy value is).
This may not be an ideal way to solve the exercise, as the code is rather long and convoluted.
However, it is a valid (and fast) solution.
Using structural pattern matching, introduced in Python 3.10, could shorten and clarify the code in this situation.
Pulling some logic out of the score function and into additional “helper” functions could also help.
Structural Pattern Matching
Structural Pattern Matching
Another very interesting approach is to use structural pattern matching.
Existing in Python since 3.10, this feature allows for neater code than traditional if structures.
By and large, we reuse the code from the if structure approach.
We set the constants to random values and check for them in the match structure.
category is the “subject”, and in every other line, we check it against a “pattern”.
ONES = 1
TWOS = 2
THREES = 3
FOURS = 4
FIVES = 5
SIXES = 6
FULL_HOUSE = 'FULL_HOUSE'
FOUR_OF_A_KIND = 'FOUR_OF_A_KIND'
LITTLE_STRAIGHT = 'LITTLE_STRAIGHT'
BIG_STRAIGHT = 'BIG_STRAIGHT'
CHOICE = 'CHOICE'
YACHT = 'YACHT'
def score(dice, category):
match category:
case 1 | 2 | 3 | 4 | 5 | 6:
return dice.count(category) * category
case 'FULL_HOUSE' if len(set(dice)) == 2 and dice.count(dice[0]) in [2, 3]:
return sum(dice)
case 'FOUR_OF_A_KIND' if dice[0] == dice[3] or dice[1] == dice[4]:
return dice[1] * 4
case 'LITTLE_STRAIGHT' if sorted(dice) == [1, 2, 3, 4, 5]:
return 30
case 'BIG_STRAIGHT' if sorted(dice) == [2, 3, 4, 5, 6]:
return 30
case 'YACHT' if all(num == dice[0] for num in dice):
return 50
case 'CHOICE':
return sum(dice)
case _:
return 0
For the first pattern, we utilize “or patterns”, using the | operator.
This checks whether the subject is any of the provided patterns.
In the next five patterns, we check an additional condition along with the pattern matching.
Finally, we use the wildcard operator _ to match anything.
As the compiler checks the patterns (cases) in order, return 0 will be executed if none of the other patterns match.
Note that the conditions might differ, but the patterns must have hard coded values - that is, you can’t say case ONES ... instead of case 1 ....
This will capture the category and lead to unexpected behavior.
This code is much clenaer than the corresponding if structure code.
Source: Exercism python/yacht