Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write approaches for Yacht #3420

Merged
merged 7 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions exercises/practice/yacht/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"introduction": {
"authors": ["safwansamsudeen"]
},
"approaches": [
{
"uuid": "3593cfe3-5cab-4141-b0a2-329148a66bb6",
"slug": "functions",
"title": "Functions",
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
"blurb": "Use functions",
"authors": ["safwansamsudeen"]
},
{
"uuid": "eccd1e1e-6c88-4823-9b25-944eccaa92e7",
"slug": "if-structure",
"title": "If structure",
"blurb": "Use an if structure",
"authors": ["safwansamsudeen"]
}
]
}
82 changes: 82 additions & 0 deletions exercises/practice/yacht/.approaches/functions/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
## Approach: functions
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
Each bit of functionality for each category can be encoded in a function, and the constant set to that function.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
We use `lambda`s as _all_ the functions can be written in one line.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
In `score`, we call the category (as it's now a function) passing in `dice`.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved

```python
def digits(num):
return lambda dice: dice.count(num) * num

YACHT = lambda dice: 50 if dice.count(dice[0]) == len(dice) else 0
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
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 * sorted(dice)[1] if len(set(dice)) < 3 and dice.count(dice[0]) in (1, 4, 5) else 0
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
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)
```
This is a succinct way to solve the exercise, although some one-liners get a little long.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
If interested, read more on [lamdas][lambdas].
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved

Instead of setting each constant in `ONES` through `SIXES` to a separate function, we create a function `digits` that returns a function, using [closures][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(d) == 20 and len(set(d)) == 5` (15 in `LITTLE_STRAIGHT`).
In `CHOICE`, `lambda x: sum(x)` is shortened to just `sum`.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved

The essence of the one-liners in `FULL_HOUSE` and `FOUR_OF_A_KIND` is in creating `set`s to remove the duplicates and checking their lengths.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
However, PEP8 doesn't recommend setting variables to `lambda`s for use as functions, so it's better practice to use `def`:
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
```python
def digits(num):
return lambda dice: dice.count(num) * num

def YACHT(dice): return 50 if dice.count(dice[0]) == len(dice) else 0
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
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
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
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)
```
`FOUR_OF_A_KIND` is commonly the hardest function to encode in one line, so it's good to break it over multiple lines:
```python
def four_of_a_kind(dice):
four_times_elements = [num for num in set(dice) if dice.count(num) >= 4]
return 4 * four_times_elements[0] if len(four_times_elements) > 0 else 0
FOUR_OF_A_KIND = four_of_a_kind
```
This approach can be done in one line using the [walrus operator][walrus] which exists in Python since 3.8 (done slightly differently to show the possible variations):
```python
def FOUR_OF_A_KIND(dice): return four_elem[0] * 4 if (four_elem := [num for num in dice if dice.count(num) >= 4]) else 0
```
BethanyG marked this conversation as resolved.
Show resolved Hide resolved

The [ternary operator][ternary-operator] is crucial in solving the exercise using one line.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
As functions are being used, we can even spread code over multiple lines as it improves readability.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
```python
# and so on
# or even, though not recommended
def YACHT(dice):
if dice.count(dice[0]) == len(dice):
return 50
return 0
```

[closures]: https://www.programiz.com/python-programming/closure
[ternary-operator]: https://www.tutorialspoint.com/ternary-operator-in-python
[lambdas]: https://www.w3schools.com/python/python_lambda.asp
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
[walrus]: https://realpython.com/python-walrus-operator/
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions exercises/practice/yacht/.approaches/functions/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
YACHT = lambda x: 50 if x.count(x[0]) == len(x) else 0
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
ONES = digits(1)
FULL_HOUSE = lambda x: sum(x) if len(set(x)) == 2 and x.count(x[0]) in [2, 3] else 0
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
LITTLE_STRAIGHT = lambda x: 30 if sorted(x) == [1, 2, 3, 4, 5] else 0
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved

def score(dice, category):
return category(dice)
57 changes: 57 additions & 0 deletions exercises/practice/yacht/.approaches/if-structure/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# If structure
The constants can be set to random, null, or numeric values, and an `if` structure inside `score` determines the code to be executed.
BethanyG marked this conversation as resolved.
Show resolved Hide resolved
As one-liners aren't necessary here, we can spread out the code to make it look neater.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
```python
YACHT = 'YACHT'
ONES = 'ONES'
TWOS = 'TWOS'
THREES = 'THREES'
FOURS = 'FOURS'
FIVES = 'FIVES'
SIXES = 'SIXES'
FULL_HOUSE = 'FULL_HOUSE'
FOUR_OF_A_KIND = 'FOUR_OF_A_KIND'
LITTLE_STRAIGHT = 'LITTLE_STRAIGHT'
BIG_STRAIGHT = 'BIG_STRAIGHT'
CHOICE = 'CHOICE'

def score(dice, category):
if category == 'ONES':
return dice.count(1)
elif category == 'TWOS':
return dice.count(2) * 2
elif category == 'THREES':
return dice.count(3) * 3
elif category == 'FOURS':
return dice.count(4) * 4
elif category == 'FIVES':
return dice.count(5) * 5
elif category == 'SIXES':
return dice.count(6) * 6
elif category == 'FULL_HOUSE':
for n1 in dice:
for n2 in dice:
if dice.count(n1) == 3 and dice.count(n2) == 2:
return n1 * 3 + n2 * 2
elif category == 'FOUR_OF_A_KIND':
for num in dice:
if dice.count(num) >= 4:
return num * 4
elif category == 'LITTLE_STRAIGHT':
# A long but alternative way
if dice.count(1) == 1 and dice.count(2) == 1 and dice.count(3) == 1 and dice.count(4) == 1 and dice.count(5) == 1:
return 30
elif category == 'BIG_STRAIGHT':
if dice.count(6) == 1 and dice.count(2) == 1 and dice.count(3) == 1 and dice.count(4) == 1 and dice.count(5) == 1:
return 30
elif category == 'YACHT':
if all(num == dice[0] for num in dice):
return 50
elif category == 'CHOICE':
return sum(dice)
return 0
```
BethanyG marked this conversation as resolved.
Show resolved Hide resolved
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 is not an ideal way to solve the exercise, as the provided constants are used as dummies, and the code is rather long and winded. It remains, however, valid, and does have its advantages, such as the lack of repetition in returning 0 that we had in the `lambda` approach.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
8 changes: 8 additions & 0 deletions exercises/practice/yacht/.approaches/if-structure/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
YACHT = 'YACHT'
CHOICE = 'CHOICE'
def score(dice, category):
if category == 'ONES':
...
elif category == 'FULL_HOUSE':
...
return 0
87 changes: 87 additions & 0 deletions exercises/practice/yacht/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Introduction
Yacht in Python can be solved in many ways. The most intuitive approach is to use an `if` structure. More idiomatically, you can create functions and set their names to the constant names.
BethanyG marked this conversation as resolved.
Show resolved Hide resolved

## General guidance
The main thing in this exercise is to map a category to a function or a standalone piece of code. While map generally reminds us of `dict`s, here the constants in the stub file are global. This indicates that the most idiomatic approach is not using a `dict`. Adhering to the principles of DRY too is important - don't repeat yourself if you can help it, especially in the `ONES` through `SIXES` categories.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved

## Approach: functions
Each bit of functionality for each category can be encoded in a function, and the constant set to that function. We use `lambda`s as _all_ of the functions can be written in one line. In `score`, we call the category (as it's now a function) passing in `dice`. We use `lambda`s as _all_ of the functions can be written in one line.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
```python
def digits(n):
return lambda x: x.count(n) * n

YACHT = lambda x: 50 if x.count(x[0]) == len(x) else 0
ONES = digits(1)
TWOS = digits(2)
THREES = digits(3)
FOURS = digits(4)
FIVES = digits(5)
SIXES = digits(6)
FULL_HOUSE = lambda x: sum(x) if len(set(x)) == 2 and x.count(x[0]) in [2, 3] else 0
FOUR_OF_A_KIND = lambda s: 4 * sorted(s)[1] if len(set(s)) < 3 and s.count(s[0]) in (1, 4, 5) else 0
LITTLE_STRAIGHT = lambda x: 30 if sorted(x) == [1, 2, 3, 4, 5] else 0
BIG_STRAIGHT = lambda x: 30 if sorted(x) == [2, 3, 4, 5, 6] else 0
CHOICE = sum

def score(dice, category):
return category(dice)
```
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
This is a very idiomatic way to solve the exercise, although some one-liners get a little long.
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
The repetitive code is minimized using a seperate function `digits` that returns a function (closure). For more information on this approach, read [this document][approach-functions].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The repetitive code is minimized using a seperate function `digits` that returns a function (closure). For more information on this approach, read [this document][approach-functions].
For more information on this approach, read [this document][approach-functions].


## Approach: if structure
The constants can be set to random, null, or numeric values, and an `if` structure inside `score` determines the code to be executed.
As one-liners aren't necessary here, we can spread out the code to make it look neater.
```python
YACHT = 'YACHT'
ONES = 'ONES'
TWOS = 'TWOS'
THREES = 'THREES'
FOURS = 'FOURS'
FIVES = 'FIVES'
SIXES = 'SIXES'
FULL_HOUSE = 'FULL_HOUSE'
FOUR_OF_A_KIND = 'FOUR_OF_A_KIND'
LITTLE_STRAIGHT = 'LITTLE_STRAIGHT'
BIG_STRAIGHT = 'BIG_STRAIGHT'
CHOICE = 'CHOICE'

def score(dice, category):
if category == 'ONES':
return dice.count(1)
elif category == 'TWOS':
return dice.count(2) * 2
elif category == 'THREES':
return dice.count(3) * 3
elif category == 'FOURS':
return dice.count(4) * 4
elif category == 'FIVES':
return dice.count(5) * 5
elif category == 'SIXES':
return dice.count(6) * 6
elif category == 'FULL_HOUSE':
for i in dice:
for j in dice:
if dice.count(i) == 3 and dice.count(j) == 2:
return i * 3 + j * 2
elif category == 'FOUR_OF_A_KIND':
for j in dice:
if dice.count(j) >= 4:
return j * 4
elif category == 'LITTLE_STRAIGHT':
if dice.count(1) == 1 and dice.count(2) == 1 and dice.count(3) == 1 and dice.count(4) == 1 and dice.count(5) == 1:
return 30
elif category == 'BIG_STRAIGHT':
if dice.count(6) == 1 and dice.count(2) == 1 and dice.count(3) == 1 and dice.count(4) == 1 and dice.count(5) == 1:
return 30
elif category == 'YACHT':
if all(i == dice[0] for i in dice):
return 50
elif category == 'CHOICE':
return sum(dice)
return 0
```
safwansamsudeen marked this conversation as resolved.
Show resolved Hide resolved
Read more on this approach [here][approach-if-structure].

[approach-functions]: https://exercism.org/tracks/python/exercises/yacht/approaches/functions
[approach-if-structure]: https://exercism.org/tracks/python/exercises/yacht/approaches/if-structure