-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Write approaches for Yacht #3420
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
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
f9473e6
write approaches for yacht
safwansamsudeen 782ceac
improve wording, structure, and code
safwansamsudeen b3ef407
update based on bethany's changes
safwansamsudeen 194f24d
Apply suggestions from code review
safwansamsudeen c6f5574
improve snippets, add spm approach
safwansamsudeen 0e634bd
Merge remote-tracking branch 'origin/main'
safwansamsudeen 6bc9914
Apply suggestions from code review
BethanyG File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,28 @@ | ||
{ | ||
"introduction": { | ||
"authors": ["safwansamsudeen"] | ||
}, | ||
"approaches": [ | ||
{ | ||
"uuid": "3593cfe3-5cab-4141-b0a2-329148a66bb6", | ||
"slug": "functions", | ||
"title": "Lambdas with Functions", | ||
"blurb": "Use lambdas with functions", | ||
"authors": ["safwansamsudeen"] | ||
}, | ||
{ | ||
"uuid": "eccd1e1e-6c88-4823-9b25-944eccaa92e7", | ||
"slug": "if-structure", | ||
"title": "If structure", | ||
"blurb": "Use an if structure", | ||
"authors": ["safwansamsudeen"] | ||
}, | ||
{ | ||
"uuid": "72079791-e51f-4825-ad94-3b7516c631cc", | ||
"slug": "structural-pattern-matching", | ||
"title": "Structural Pattern Matching", | ||
"blurb": "Use structural pattern matching", | ||
"authors": ["safwansamsudeen"] | ||
} | ||
] | ||
} |
This file contains hidden or 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 @@ | ||
## 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][lambda] 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. | ||
|
||
```python | ||
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][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][pep8] does not recommend assigning constant or variable names to `lambda` expressions, so it is a better practice to use `def`: | ||
```python | ||
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][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. | ||
```python | ||
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 | ||
[lambda]: https://docs.python.org/3/howto/functional.html?highlight=lambda#small-functions-and-the-lambda-expression | ||
[pep8]: https://peps.python.org/pep-0008/ |
This file contains hidden or 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 digits(num): | ||
return lambda dice: dice.count(num) * num | ||
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) |
50 changes: 50 additions & 0 deletions
50
exercises/practice/yacht/.approaches/if-structure/content.md
This file contains hidden or 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 @@ | ||
# 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: | ||
```python | ||
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][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]: https://peps.python.org/pep-0636/ |
8 changes: 8 additions & 0 deletions
8
exercises/practice/yacht/.approaches/if-structure/snippet.txt
This file contains hidden or 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 @@ | ||
ONES = 1 | ||
YACHT = 'YACHT' | ||
def score(dice, category): | ||
if category == 'ONES': | ||
... | ||
elif category == 'FULL_HOUSE': | ||
... | ||
return 0 |
This file contains hidden or 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,77 @@ | ||
# Introduction | ||
Yacht in Python can be solved in many ways. The most intuitive approach is to use an `if` structure. | ||
Alternatively, you can create functions and set their names to the constant names. | ||
|
||
## General guidance | ||
The main thing in this exercise is to map a category (_here defined as constants in the stub file_) to a function or a standalone piece of code. | ||
While mapping generally reminds us of dictionaries, here the constants are global. | ||
This indicates that the most idiomatic approach is not using a `dict`. | ||
Adhering to the principles of DRY is important - don't repeat yourself if you can help it, especially in the `ONES` through `SIXES` categories! | ||
|
||
## Approach: functions | ||
Each bit of functionality for each category can be encoded in a function, and the constant name set to that function. | ||
This can be done by assigning the constant name to a `lambda` or creating a one-line function using the constant as a function name. | ||
```python | ||
```python | ||
def digits(num): | ||
return lambda dice: dice.count(num) * num | ||
YACHT = lambda dice: 50 if dice.count(dice[0]) == len(dice) 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) | ||
``` | ||
This is a very succinct way to solve the exercise, although some one-liners get a little long. | ||
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 | ||
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 | ||
``` | ||
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 |
55 changes: 55 additions & 0 deletions
55
exercises/practice/yacht/.approaches/structural-pattern-matching/content.md
This file contains hidden or 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,55 @@ | ||
# Structural Pattern Matching | ||
|
||
Another very interesting approach is to use [structural pattern matching][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][approach-if-structure]. | ||
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". | ||
```python | ||
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 (`case`s) 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. | ||
|
||
[structural pattern matching]: https://peps.python.org/pep-0636/ | ||
[approach-if-structure]: https://exercism.org/tracks/python/exercises/yacht/approaches/if-structure |
8 changes: 8 additions & 0 deletions
8
exercises/practice/yacht/.approaches/structural-pattern-matching/snippet.txt
This file contains hidden or 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 @@ | ||
ONES = 1 | ||
YACHT = 'YACHT' | ||
def score(dice, category): | ||
match category: | ||
case 1 | 2 | 3 | 4 | 5 | 6: | ||
return dice.count(category) * category | ||
case _: | ||
return 0 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.