From f9473e6e2c4e833f115d172ddbce742cc397e004 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Mon, 8 May 2023 13:38:15 +0400 Subject: [PATCH 1/6] write approaches for yacht --- .../practice/yacht/.approaches/config.json | 21 +++++ .../yacht/.approaches/functions/content.md | 66 ++++++++++++++ .../yacht/.approaches/functions/snippet.txt | 7 ++ .../yacht/.approaches/if-structure/content.md | 56 ++++++++++++ .../.approaches/if-structure/snippet.txt | 8 ++ .../yacht/.approaches/introduction.md | 87 +++++++++++++++++++ 6 files changed, 245 insertions(+) create mode 100644 exercises/practice/yacht/.approaches/config.json create mode 100644 exercises/practice/yacht/.approaches/functions/content.md create mode 100644 exercises/practice/yacht/.approaches/functions/snippet.txt create mode 100644 exercises/practice/yacht/.approaches/if-structure/content.md create mode 100644 exercises/practice/yacht/.approaches/if-structure/snippet.txt create mode 100644 exercises/practice/yacht/.approaches/introduction.md diff --git a/exercises/practice/yacht/.approaches/config.json b/exercises/practice/yacht/.approaches/config.json new file mode 100644 index 0000000000..91a1be5aff --- /dev/null +++ b/exercises/practice/yacht/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": ["safwansamsudeen"] + }, + "approaches": [ + { + "uuid": "3593cfe3-5cab-4141-b0a2-329148a66bb6", + "slug": "functions", + "title": "Functions", + "blurb": "Use functions", + "authors": ["safwansamsudeen"] + }, + { + "uuid": "eccd1e1e-6c88-4823-9b25-944eccaa92e7", + "slug": "if-structure", + "title": "If structure", + "blurb": "Use an if structure", + "authors": ["safwansamsudeen"] + } + ] +} diff --git a/exercises/practice/yacht/.approaches/functions/content.md b/exercises/practice/yacht/.approaches/functions/content.md new file mode 100644 index 0000000000..9c52a476a6 --- /dev/null +++ b/exercises/practice/yacht/.approaches/functions/content.md @@ -0,0 +1,66 @@ +## 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_ the functions can be written in one line. +In `score`, we call the category (as it's now a function) passing in `dice`. + +```python +def digits(n): + return lambda dice: dice.count(n) * n + +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 s: 4 * sorted(s)[1] if len(set(s)) < 3 and s.count(s[0]) in (1, 4, 5) 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 idiomatic way to solve the exercise, although some one-liners get a little long. +The [ternary operator][ternary-operator] is crucial in solving the exercise this way. +Instead of `lambda`s, functions could be created and the constants set to them. +This will remove the need for one-liners. If interested, read more on [lamdas][lambdas]. +```python +def yacht(dice): + if dice.count(dice[0]) == len(dice): + return 50 + return 0 +YACHT = yacht +# and so on +# or even, though not recommended +def YACHT(dice): + if dice.count(dice[0]) == len(dice): + return 50 + return 0 +``` + +Instead of setting each constant in `ONES` through `SIXES` to a separate `lambda`, we create a function that returns a `lambda`, 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`. + +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. +`FOUR_OF_A_KIND` is commonly the hardest function to put in one line, and it's valid to declare it in a less complicated function: +```python +FOUR_OF_A_KIND = four_of_a_kind +def four_of_a_kind(x): + four_times_elements = [dice for dice in set(x) if x.count(dice) >= 4] + return 4 * four_times_elements[0] if len(four_times_elements) > 0 else 0 +``` +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 +FOUR_OF_A_KIND = lambda dice: four_elem[0] * 4 if (four_elem := [i for i in dice if dice.count(i) >= 4]) else 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 +[walrus]: https://realpython.com/python-walrus-operator/ \ No newline at end of file diff --git a/exercises/practice/yacht/.approaches/functions/snippet.txt b/exercises/practice/yacht/.approaches/functions/snippet.txt new file mode 100644 index 0000000000..d601ab5d68 --- /dev/null +++ b/exercises/practice/yacht/.approaches/functions/snippet.txt @@ -0,0 +1,7 @@ +YACHT = lambda x: 50 if x.count(x[0]) == len(x) else 0 +ONES = digits(1) +FULL_HOUSE = lambda x: sum(x) if len(set(x)) == 2 and x.count(x[0]) in [2, 3] else 0 +LITTLE_STRAIGHT = lambda x: 30 if sorted(x) == [1, 2, 3, 4, 5] else 0 + +def score(dice, category): + return category(dice) \ No newline at end of file diff --git a/exercises/practice/yacht/.approaches/if-structure/content.md b/exercises/practice/yacht/.approaches/if-structure/content.md new file mode 100644 index 0000000000..65de1d90a8 --- /dev/null +++ b/exercises/practice/yacht/.approaches/if-structure/content.md @@ -0,0 +1,56 @@ +# 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 +``` +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. \ No newline at end of file diff --git a/exercises/practice/yacht/.approaches/if-structure/snippet.txt b/exercises/practice/yacht/.approaches/if-structure/snippet.txt new file mode 100644 index 0000000000..718f7b1ab9 --- /dev/null +++ b/exercises/practice/yacht/.approaches/if-structure/snippet.txt @@ -0,0 +1,8 @@ +YACHT = 'YACHT' +CHOICE = 'CHOICE' +def score(dice, category): + if category == 'ONES': + ... + elif category == 'FULL_HOUSE': + ... + return 0 \ No newline at end of file diff --git a/exercises/practice/yacht/.approaches/introduction.md b/exercises/practice/yacht/.approaches/introduction.md new file mode 100644 index 0000000000..5dfea41d89 --- /dev/null +++ b/exercises/practice/yacht/.approaches/introduction.md @@ -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. + +## 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. + +## 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. +```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) +``` +This is a very idiomatic way to solve the exercise, although some one-liners get a little long. +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]. + +## 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 +``` +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 From 782ceac9e12e3ebfc87c17face469de115479e8d Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Wed, 10 May 2023 09:41:30 +0400 Subject: [PATCH 2/6] improve wording, structure, and code --- .../yacht/.approaches/functions/content.md | 70 ++++++++++++------- .../yacht/.approaches/if-structure/content.md | 17 ++--- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/exercises/practice/yacht/.approaches/functions/content.md b/exercises/practice/yacht/.approaches/functions/content.md index 9c52a476a6..b645096a48 100644 --- a/exercises/practice/yacht/.approaches/functions/content.md +++ b/exercises/practice/yacht/.approaches/functions/content.md @@ -4,8 +4,8 @@ We use `lambda`s as _all_ the functions can be written in one line. In `score`, we call the category (as it's now a function) passing in `dice`. ```python -def digits(n): - return lambda dice: dice.count(n) * n +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) @@ -15,7 +15,7 @@ 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 s: 4 * sorted(s)[1] if len(set(s)) < 3 and s.count(s[0]) in (1, 4, 5) 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 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 @@ -23,42 +23,58 @@ CHOICE = sum def score(dice, category): return category(dice) ``` -This is a very idiomatic way to solve the exercise, although some one-liners get a little long. -The [ternary operator][ternary-operator] is crucial in solving the exercise this way. -Instead of `lambda`s, functions could be created and the constants set to them. -This will remove the need for one-liners. If interested, read more on [lamdas][lambdas]. -```python -def yacht(dice): - if dice.count(dice[0]) == len(dice): - return 50 - return 0 -YACHT = yacht -# and so on -# or even, though not recommended -def YACHT(dice): - if dice.count(dice[0]) == len(dice): - return 50 - return 0 -``` +This is a succinct way to solve the exercise, although some one-liners get a little long. +If interested, read more on [lamdas][lambdas]. -Instead of setting each constant in `ONES` through `SIXES` to a separate `lambda`, we create a function that returns a `lambda`, using [closures][closures] transparently. +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`. -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. -`FOUR_OF_A_KIND` is commonly the hardest function to put in one line, and it's valid to declare it in a less complicated function: +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. +However, PEP8 doesn't recommend setting variables to `lambda`s for use as functions, so it's better practice to use `def`: ```python -FOUR_OF_A_KIND = four_of_a_kind -def four_of_a_kind(x): - four_times_elements = [dice for dice in set(x) if x.count(dice) >= 4] +def digits(num): + return lambda dice: dice.count(num) * num + +def YACHT(dice): return 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) +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) +``` +`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 -FOUR_OF_A_KIND = lambda dice: four_elem[0] * 4 if (four_elem := [i for i in dice if dice.count(i) >= 4]) else 0 +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 ``` +The [ternary operator][ternary-operator] is crucial in solving the exercise using one line. +As functions are being used, we can even spread code over multiple lines as it improves readability. +```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 diff --git a/exercises/practice/yacht/.approaches/if-structure/content.md b/exercises/practice/yacht/.approaches/if-structure/content.md index 65de1d90a8..a9b3e7daa9 100644 --- a/exercises/practice/yacht/.approaches/if-structure/content.md +++ b/exercises/practice/yacht/.approaches/if-structure/content.md @@ -29,22 +29,23 @@ def score(dice, category): 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 + 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 j in dice: - if dice.count(j) >= 4: - return j * 4 + 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(i == dice[0] for i in dice): + if all(num == dice[0] for num in dice): return 50 elif category == 'CHOICE': return sum(dice) From b3ef4071cb569e6a6393934d5c12d8aba84ce13e Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Thu, 25 May 2023 20:47:36 +0530 Subject: [PATCH 3/6] update based on bethany's changes --- .../practice/yacht/.approaches/config.json | 4 +- .../yacht/.approaches/functions/content.md | 50 +++++------- .../yacht/.approaches/if-structure/content.md | 61 +++++++------- .../yacht/.approaches/introduction.md | 81 ++++++++----------- 4 files changed, 84 insertions(+), 112 deletions(-) diff --git a/exercises/practice/yacht/.approaches/config.json b/exercises/practice/yacht/.approaches/config.json index 91a1be5aff..67e17189c3 100644 --- a/exercises/practice/yacht/.approaches/config.json +++ b/exercises/practice/yacht/.approaches/config.json @@ -6,8 +6,8 @@ { "uuid": "3593cfe3-5cab-4141-b0a2-329148a66bb6", "slug": "functions", - "title": "Functions", - "blurb": "Use functions", + "title": "Lambdas with Functions", + "blurb": "Use lambdas with functions", "authors": ["safwansamsudeen"] }, { diff --git a/exercises/practice/yacht/.approaches/functions/content.md b/exercises/practice/yacht/.approaches/functions/content.md index b645096a48..be297e956e 100644 --- a/exercises/practice/yacht/.approaches/functions/content.md +++ b/exercises/practice/yacht/.approaches/functions/content.md @@ -1,13 +1,13 @@ -## 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_ the functions can be written in one line. -In `score`, we call the category (as it's now a function) passing in `dice`. +## 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 dice.count(dice[0]) == len(dice) else 0 +YACHT = lambda dice: 50 if len(set(dice)) == 1 else 0 ONES = digits(1) TWOS = digits(2) THREES = digits(3) @@ -15,29 +15,32 @@ 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 +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 succinct way to solve the exercise, although some one-liners get a little long. -If interested, read more on [lamdas][lambdas]. +``` + 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`). +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`. -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. -However, PEP8 doesn't recommend setting variables to `lambda`s for use as functions, so it's better practice to use `def`: +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 dice.count(dice[0]) == len(dice) else 0 +def YACHT(dice): return 50 if len(set(dice)) == 1 else 0 ONES = digits(1) TWOS = digits(2) THREES = digits(3) @@ -53,23 +56,10 @@ 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 -``` -The [ternary operator][ternary-operator] is crucial in solving the exercise using one line. -As functions are being used, we can even spread code over multiple lines as it improves readability. +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 -# and so on -# or even, though not recommended def YACHT(dice): if dice.count(dice[0]) == len(dice): return 50 @@ -78,5 +68,5 @@ def YACHT(dice): [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 -[walrus]: https://realpython.com/python-walrus-operator/ \ No newline at end of file +[lambda]: https://docs.python.org/3/howto/functional.html?highlight=lambda#small-functions-and-the-lambda-expression +[pep8]: https://peps.python.org/pep-0008/ \ No newline at end of file diff --git a/exercises/practice/yacht/.approaches/if-structure/content.md b/exercises/practice/yacht/.approaches/if-structure/content.md index a9b3e7daa9..581f31d192 100644 --- a/exercises/practice/yacht/.approaches/if-structure/content.md +++ b/exercises/practice/yacht/.approaches/if-structure/content.md @@ -1,49 +1,37 @@ # 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. + +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 -YACHT = 'YACHT' -ONES = 'ONES' -TWOS = 'TWOS' -THREES = 'THREES' -FOURS = 'FOURS' -FIVES = 'FIVES' -SIXES = 'SIXES' +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 == '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 + if category in (1,2,3,4,5,6): + return dice.count(category) * category 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 + if len(set(dice)) == 2 and dice.count(dice[0]) in [2, 3]: + return sum(dice) or 0 elif category == 'FOUR_OF_A_KIND': - for num in dice: - if dice.count(num) >= 4: - return num * 4 + if dice[0] == dice[3] or dice[1] == dice[4]: + return dice[1] * 4 or 0 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 + if sorted(dice) == [1, 2, 3, 4, 5]: + return 30 or 0 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 + 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 @@ -54,4 +42,9 @@ def score(dice, category): 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. \ No newline at end of file +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/ \ No newline at end of file diff --git a/exercises/practice/yacht/.approaches/introduction.md b/exercises/practice/yacht/.approaches/introduction.md index 5dfea41d89..ce57ac9f0d 100644 --- a/exercises/practice/yacht/.approaches/introduction.md +++ b/exercises/practice/yacht/.approaches/introduction.md @@ -2,80 +2,69 @@ 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. ## 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. +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 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. +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 -def digits(n): - return lambda x: x.count(n) * n - -YACHT = lambda x: 50 if x.count(x[0]) == len(x) else 0 +```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 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 +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 idiomatic way to solve the exercise, although some one-liners get a little long. -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]. +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. +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' +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 == '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 + if category in (1,2,3,4,5,6): + return dice.count(category) * category 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 + if len(set(dice)) == 2 and dice.count(dice[0]) in [2, 3]: + return sum(dice) or 0 elif category == 'FOUR_OF_A_KIND': - for j in dice: - if dice.count(j) >= 4: - return j * 4 + if dice[0] == dice[3] or dice[1] == dice[4]: + return dice[1] * 4 or 0 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 + if sorted(dice) == [1, 2, 3, 4, 5]: + return 30 or 0 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 + if sorted(dice) == [2, 3, 4, 5, 6]: + return 30 or 0 elif category == 'YACHT': - if all(i == dice[0] for i in dice): + if all(num == dice[0] for num in dice): return 50 elif category == 'CHOICE': return sum(dice) From 194f24dc710eedf55590d0c62446126d7a36f697 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Wed, 31 May 2023 15:55:29 +0530 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: BethanyG --- exercises/practice/yacht/.approaches/functions/content.md | 4 ++-- exercises/practice/yacht/.approaches/functions/snippet.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/practice/yacht/.approaches/functions/content.md b/exercises/practice/yacht/.approaches/functions/content.md index be297e956e..2c6bfe527d 100644 --- a/exercises/practice/yacht/.approaches/functions/content.md +++ b/exercises/practice/yacht/.approaches/functions/content.md @@ -28,8 +28,8 @@ def score(dice, category): 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`. +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. diff --git a/exercises/practice/yacht/.approaches/functions/snippet.txt b/exercises/practice/yacht/.approaches/functions/snippet.txt index d601ab5d68..84dc9b670d 100644 --- a/exercises/practice/yacht/.approaches/functions/snippet.txt +++ b/exercises/practice/yacht/.approaches/functions/snippet.txt @@ -1,7 +1,7 @@ -YACHT = lambda x: 50 if x.count(x[0]) == len(x) else 0 +YACHT = lambda dice: 50 if dice.count(dice[0]) == len(dice) else 0 ONES = digits(1) -FULL_HOUSE = lambda x: sum(x) if len(set(x)) == 2 and x.count(x[0]) in [2, 3] else 0 -LITTLE_STRAIGHT = lambda x: 30 if sorted(x) == [1, 2, 3, 4, 5] else 0 +FULL_HOUSE = lambda dice: sum(dice) if len(set(dice)) == 2 and dice.count(dice[0]) in [2, 3] else 0 +LITTLE_STRAIGHT = lambda dice: 30 if sorted(dice) == [1, 2, 3, 4, 5] else 0 def score(dice, category): return category(dice) \ No newline at end of file From c6f5574bc11d4a609f8fe852f57a58720f4679b4 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Wed, 31 May 2023 18:02:54 +0530 Subject: [PATCH 5/6] improve snippets, add spm approach --- .../practice/yacht/.approaches/config.json | 7 +++ .../yacht/.approaches/functions/snippet.txt | 3 +- .../.approaches/if-structure/snippet.txt | 2 +- .../structural-pattern-matching/content.md | 55 +++++++++++++++++++ .../structural-pattern-matching/snippet.txt | 8 +++ 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 exercises/practice/yacht/.approaches/structural-pattern-matching/content.md create mode 100644 exercises/practice/yacht/.approaches/structural-pattern-matching/snippet.txt diff --git a/exercises/practice/yacht/.approaches/config.json b/exercises/practice/yacht/.approaches/config.json index 67e17189c3..86d37075b8 100644 --- a/exercises/practice/yacht/.approaches/config.json +++ b/exercises/practice/yacht/.approaches/config.json @@ -16,6 +16,13 @@ "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"] } ] } diff --git a/exercises/practice/yacht/.approaches/functions/snippet.txt b/exercises/practice/yacht/.approaches/functions/snippet.txt index d601ab5d68..34d270ad89 100644 --- a/exercises/practice/yacht/.approaches/functions/snippet.txt +++ b/exercises/practice/yacht/.approaches/functions/snippet.txt @@ -1,7 +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 ONES = digits(1) FULL_HOUSE = lambda x: sum(x) if len(set(x)) == 2 and x.count(x[0]) in [2, 3] else 0 LITTLE_STRAIGHT = lambda x: 30 if sorted(x) == [1, 2, 3, 4, 5] else 0 - def score(dice, category): return category(dice) \ No newline at end of file diff --git a/exercises/practice/yacht/.approaches/if-structure/snippet.txt b/exercises/practice/yacht/.approaches/if-structure/snippet.txt index 718f7b1ab9..fb590cc155 100644 --- a/exercises/practice/yacht/.approaches/if-structure/snippet.txt +++ b/exercises/practice/yacht/.approaches/if-structure/snippet.txt @@ -1,5 +1,5 @@ +ONES = 1 YACHT = 'YACHT' -CHOICE = 'CHOICE' def score(dice, category): if category == 'ONES': ... diff --git a/exercises/practice/yacht/.approaches/structural-pattern-matching/content.md b/exercises/practice/yacht/.approaches/structural-pattern-matching/content.md new file mode 100644 index 0000000000..b49bb6340b --- /dev/null +++ b/exercises/practice/yacht/.approaches/structural-pattern-matching/content.md @@ -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 \ No newline at end of file diff --git a/exercises/practice/yacht/.approaches/structural-pattern-matching/snippet.txt b/exercises/practice/yacht/.approaches/structural-pattern-matching/snippet.txt new file mode 100644 index 0000000000..4ed99824d8 --- /dev/null +++ b/exercises/practice/yacht/.approaches/structural-pattern-matching/snippet.txt @@ -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 \ No newline at end of file From 6bc99141f87afd34caf45208c93506cdde841f6a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 3 Jun 2023 18:47:00 -0700 Subject: [PATCH 6/6] Apply suggestions from code review --- exercises/practice/yacht/.approaches/introduction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/yacht/.approaches/introduction.md b/exercises/practice/yacht/.approaches/introduction.md index ce57ac9f0d..9717760637 100644 --- a/exercises/practice/yacht/.approaches/introduction.md +++ b/exercises/practice/yacht/.approaches/introduction.md @@ -1,5 +1,6 @@ # 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. +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.