From ee2f6ef3df0d2f1fece1271dd844a9aca4b4c890 Mon Sep 17 00:00:00 2001 From: Rodrigo Santiago Date: Thu, 27 Oct 2022 17:43:13 -0300 Subject: [PATCH 001/826] Fix dish_ingredients type on categorize_dish docstring --- exercises/concept/cater-waiter/sets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/cater-waiter/sets.py b/exercises/concept/cater-waiter/sets.py index b0202e6a5f..faa0114d9c 100644 --- a/exercises/concept/cater-waiter/sets.py +++ b/exercises/concept/cater-waiter/sets.py @@ -14,7 +14,7 @@ def clean_ingredients(dish_name, dish_ingredients): """Remove duplicates from `dish_ingredients`. :param dish_name: str - containing the dish name. - :param dish_ingredients: list - dish ingredients. + :param dish_ingredients: set - dish ingredients. :return: tuple - containing (dish_name, ingredient set). This function should return a `tuple` with the name of the dish as the first item, From b5ddf119a26fc1d9970cb4b568f3d6f057b70c75 Mon Sep 17 00:00:00 2001 From: Rodrigo Santiago Date: Thu, 27 Oct 2022 17:50:54 -0300 Subject: [PATCH 002/826] Update ethod instruction changing from list to set in example --- exercises/concept/cater-waiter/.docs/instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/cater-waiter/.docs/instructions.md b/exercises/concept/cater-waiter/.docs/instructions.md index 07e2f6bd1a..2d54bb0ed2 100644 --- a/exercises/concept/cater-waiter/.docs/instructions.md +++ b/exercises/concept/cater-waiter/.docs/instructions.md @@ -50,11 +50,11 @@ All dishes will "fit" into one of the categories imported from `sets_categories_ >>> from sets_categories_data import VEGAN, VEGETARIAN, PALEO, KETO, OMNIVORE ->>> categorize_dish('Sticky Lemon Tofu', ['tofu', 'soy sauce', 'salt', 'black pepper', 'cornstarch', 'vegetable oil', 'garlic', 'ginger', 'water', 'vegetable stock', 'lemon juice', 'lemon zest', 'sugar']) +>>> categorize_dish('Sticky Lemon Tofu', {'tofu', 'soy sauce', 'salt', 'black pepper', 'cornstarch', 'vegetable oil', 'garlic', 'ginger', 'water', 'vegetable stock', 'lemon juice', 'lemon zest', 'sugar'}) ... 'Sticky Lemon Tofu: VEGAN' ->>> categorize_dish('Shrimp Bacon and Crispy Chickpea Tacos with Salsa de Guacamole', ['shrimp', 'bacon', 'avocado', 'chickpeas', 'fresh tortillas', 'sea salt', 'guajillo chile', 'slivered almonds', 'olive oil', 'butter', 'black pepper', 'garlic', 'onion']) +>>> categorize_dish('Shrimp Bacon and Crispy Chickpea Tacos with Salsa de Guacamole', {'shrimp', 'bacon', 'avocado', 'chickpeas', 'fresh tortillas', 'sea salt', 'guajillo chile', 'slivered almonds', 'olive oil', 'butter', 'black pepper', 'garlic', 'onion'}) ... 'Shrimp Bacon and Crispy Chickpea Tacos with Salsa de Guacamole: OMNIVORE' ``` From c381955d737039279ac2f6af4b278d3833199a3e Mon Sep 17 00:00:00 2001 From: Julian <55046135+0x1AE7F@users.noreply.github.com> Date: Sun, 30 Oct 2022 18:31:44 +0100 Subject: [PATCH 003/826] Fix typo Changed "neutrinos" (line 34) to "neutrons" --- exercises/concept/meltdown-mitigation/conditionals_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/meltdown-mitigation/conditionals_test.py b/exercises/concept/meltdown-mitigation/conditionals_test.py index 682144c5a6..9837c34302 100644 --- a/exercises/concept/meltdown-mitigation/conditionals_test.py +++ b/exercises/concept/meltdown-mitigation/conditionals_test.py @@ -31,7 +31,7 @@ def test_is_criticality_balanced(self): # pylint: disable=assignment-from-no-return actual_result = is_criticality_balanced(temp, neutrons_emitted) failure_message = (f'Expected {expected} but returned {actual_result} ' - f'with T={temp} and neutrinos={neutrons_emitted}') + f'with T={temp} and neutrons={neutrons_emitted}') self.assertEqual(actual_result, expected, failure_message) @pytest.mark.task(taskno=2) From ab76008757f747488de614dcca74192221b10d1f Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:23:36 -0600 Subject: [PATCH 004/826] Add approaches to Bob (#3200) Add approaches to Bob --- .../bob/.approaches/answer-list/content.md | 67 ++++++++++++++ .../bob/.approaches/answer-list/snippet.txt | 6 ++ .../practice/bob/.approaches/config.json | 29 ++++++ .../if-statements-nested/content.md | 48 ++++++++++ .../if-statements-nested/snippet.txt | 8 ++ .../bob/.approaches/if-statements/content.md | 51 ++++++++++ .../bob/.approaches/if-statements/snippet.txt | 7 ++ .../practice/bob/.approaches/introduction.md | 92 +++++++++++++++++++ exercises/practice/bob/.articles/config.json | 11 +++ .../.articles/performance/code/Benchmark.py | 62 +++++++++++++ .../bob/.articles/performance/content.md | 31 +++++++ .../bob/.articles/performance/snippet.md | 5 + 12 files changed, 417 insertions(+) create mode 100644 exercises/practice/bob/.approaches/answer-list/content.md create mode 100644 exercises/practice/bob/.approaches/answer-list/snippet.txt create mode 100644 exercises/practice/bob/.approaches/config.json create mode 100644 exercises/practice/bob/.approaches/if-statements-nested/content.md create mode 100644 exercises/practice/bob/.approaches/if-statements-nested/snippet.txt create mode 100644 exercises/practice/bob/.approaches/if-statements/content.md create mode 100644 exercises/practice/bob/.approaches/if-statements/snippet.txt create mode 100644 exercises/practice/bob/.approaches/introduction.md create mode 100644 exercises/practice/bob/.articles/config.json create mode 100644 exercises/practice/bob/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/bob/.articles/performance/content.md create mode 100644 exercises/practice/bob/.articles/performance/snippet.md diff --git a/exercises/practice/bob/.approaches/answer-list/content.md b/exercises/practice/bob/.approaches/answer-list/content.md new file mode 100644 index 0000000000..de2049a7ad --- /dev/null +++ b/exercises/practice/bob/.approaches/answer-list/content.md @@ -0,0 +1,67 @@ +# Answer list + +```python +_ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!', + "Calm down, I know what I'm doing!"] + + +def response(hey_bob): + hey_bob = hey_bob.rstrip() + if not hey_bob: + return 'Fine. Be that way!' + isShout = (2 if hey_bob.isupper() else 0) + isQuestion = (1 if hey_bob.endswith('?') else 0) + return _ANSWERS[isShout + isQuestion] + +``` + +In this approach you define a [list][list] that contains Bob’s answers, and each condition is given a score. +The correct answer is selected from the list by using the score as the list index. + +Python doesn't _enforce_ having real constant values, +but the `_ANSWERS` list is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. +It indicates that the value is not intended to be changed. +Python also does not have real [private][private] variables, +but a leading underscore is the naming convention for indicating that a variable is not meant to be part of the public API. +It should be considered an implementation detail and subject to change without notice. + +The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input. +If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing. +Since it doesn't matter if there is leading whitespace, the `rstrip` function is used instead of [`strip`][strip]. + +A [ternary operator][ternary] is used for determining the score for a shout and a question. + +The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase. + +```exercism/note +A cased character is one which differs between lowercase and uppercase. +For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase. +`a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase. +``` + +If `isupper` is `True`, then `isShout` is given the value of `2`; otherwise, it is given the value of `0`. + +The [`endswith`][endswith] method is used to determine if the input ends with a question mark. +If the test for `endswith('?')` is `True`, then `isQuestion` is given the value of `1`; otherwise it is given the value of `0`. + + +The response is selected from the list by the index like so + +| isShout | isQuestion | Index | Answer | +| ------- | ---------- | --------- | ------------------------------------- | +| `false` | `false` | 0 + 0 = 0 | `"Whatever."` | +| `false` | `true` | 0 + 1 = 1 | `"Sure."` | +| `true` | `false` | 2 + 0 = 2 | `"Whoa, chill out!"` | +| `true` | `true` | 2 + 1 = 3 | `"Calm down, I know what I'm doing!"` | + + +[list]: https://docs.python.org/3/library/stdtypes.html?highlight=list#list +[const]: https://realpython.com/python-constants/ +[private]: https://docs.python.org/3/tutorial/classes.html#private-variables +[rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[not]: https://docs.python.org/3/reference/expressions.html#not +[strip]: https://docs.python.org/3/library/stdtypes.html?highlight=strip#str.strip +[ternary]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ +[isupper]: https://docs.python.org/3/library/stdtypes.html?highlight=isupper#str.isupper +[endswith]: https://docs.python.org/3/library/stdtypes.html?highlight=endswith#str.endswith diff --git a/exercises/practice/bob/.approaches/answer-list/snippet.txt b/exercises/practice/bob/.approaches/answer-list/snippet.txt new file mode 100644 index 0000000000..0d834fa271 --- /dev/null +++ b/exercises/practice/bob/.approaches/answer-list/snippet.txt @@ -0,0 +1,6 @@ +_ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!', + "Calm down, I know what I'm doing!"] +# code snipped +isShout = (2 if hey_bob.isupper() else 0) +isQuestion = (1 if hey_bob.endswith('?') else 0) +return _ANSWERS[isShout + isQuestion] diff --git a/exercises/practice/bob/.approaches/config.json b/exercises/practice/bob/.approaches/config.json new file mode 100644 index 0000000000..1c13c45f93 --- /dev/null +++ b/exercises/practice/bob/.approaches/config.json @@ -0,0 +1,29 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "972b7546-67ca-4364-9367-02d68a8c0314", + "slug": "if-statements", + "title": "If statements", + "blurb": "Use if statements to return the answer.", + "authors": ["bobahop"] + }, + { + "uuid": "138fcf61-262c-47f6-aac3-96528b24ee6b", + "slug": "if-statements-nested", + "title": "if statements nested", + "blurb": "Use nested if statements to return the answer.", + "authors": ["bobahop"] + }, + { + "uuid": "d74a1d7d-af85-417c-8deb-164536e4ab1b", + "slug": "answer-list", + "title": "Answer list", + "blurb": "Index into a list to return the answer.", + "authors": ["bobahop"] + } + ] +} diff --git a/exercises/practice/bob/.approaches/if-statements-nested/content.md b/exercises/practice/bob/.approaches/if-statements-nested/content.md new file mode 100644 index 0000000000..e070216986 --- /dev/null +++ b/exercises/practice/bob/.approaches/if-statements-nested/content.md @@ -0,0 +1,48 @@ +# `if` statements nested + +```python +def response(hey_bob): + hey_bob = hey_bob.rstrip() + if not hey_bob: + return 'Fine. Be that way!' + isShout = hey_bob.isupper() + isQuestion = hey_bob.endswith('?') + if isShout: + if isQuestion: + return "Calm down, I know what I'm doing!" + else: + return 'Whoa, chill out!' + if isQuestion: + return 'Sure.' + return 'Whatever.' + +``` + +In this approach you have a series of `if` statements using the calculated variables to evaluate the conditions, some of which are nested. +As soon as a `True` condition is found, the correct response is returned. + +The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input. +If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing. +Since it doesn't matter if there is leading whitespace, the `rstrip` function is used instead of [`strip`][strip]. + +The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase. + +```exercism/note +A cased character is one which differs between lowercase and uppercase. +For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase. +`a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase. +``` + +The [`endswith`][endswith] method is used to determine if the input ends with a question mark. + +Instead of testing a shout and a question on the same line, this approach first tests if the input is a shout. +If it is a shout, then the nested `if`/[`else`][else] statement returns if it is a shouted question or just a shout. +If it is not a shout, then the flow of execution skips down to the non-nested test for if the input is a question. + +[rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[not]: https://docs.python.org/3/reference/expressions.html#not +[strip]: https://docs.python.org/3/library/stdtypes.html?highlight=strip#str.strip +[isupper]: https://docs.python.org/3/library/stdtypes.html?highlight=isupper#str.isupper +[endswith]: https://docs.python.org/3/library/stdtypes.html?highlight=endswith#str.endswith +[else]: https://docs.python.org/3/reference/compound_stmts.html#else diff --git a/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt b/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt new file mode 100644 index 0000000000..c86b57e682 --- /dev/null +++ b/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt @@ -0,0 +1,8 @@ +if isShout: + if isQuestion: + return "Calm down, I know what I'm doing!" + else: + return 'Whoa, chill out!' +if isQuestion: + return 'Sure.' +return 'Whatever.' diff --git a/exercises/practice/bob/.approaches/if-statements/content.md b/exercises/practice/bob/.approaches/if-statements/content.md new file mode 100644 index 0000000000..8bae511649 --- /dev/null +++ b/exercises/practice/bob/.approaches/if-statements/content.md @@ -0,0 +1,51 @@ +# `if` statements + +```python +def response(hey_bob): + hey_bob = hey_bob.rstrip() + if not hey_bob: + return 'Fine. Be that way!' + isShout = hey_bob.isupper() + isQuestion = hey_bob.endswith('?') + if isShout and isQuestion: + return "Calm down, I know what I'm doing!" + if isShout: + return 'Whoa, chill out!' + if isQuestion: + return 'Sure.' + return 'Whatever.' + +``` + +In this approach you have a series of `if` statements using the calculated variables to evaluate the conditions. +As soon as a `True` condition is found, the correct response is returned. + +```exercism/note +Note that there are no `elif` or `else` statements. +If an `if` statement can return, then an `elif` or `else` is not needed. +Execution will either return or will continue to the next statement anyway. +``` + +The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input. +If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing. +Since it doesn't matter if there is leading whitespace, the `rstrip` function is used instead of [`strip`][strip]. + +The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase. + +```exercism/note +A cased character is one which differs between lowercase and uppercase. +For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase. +`a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase. +``` + +The [`endswith`][endswith] method is used to determine if the input ends with a question mark. + +The [logical AND operator][and] (`and`) is used to test a shout and a question together. + +[rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[not]: https://docs.python.org/3/reference/expressions.html#not +[strip]: https://docs.python.org/3/library/stdtypes.html?highlight=strip#str.strip +[isupper]: https://docs.python.org/3/library/stdtypes.html?highlight=isupper#str.isupper +[endswith]: https://docs.python.org/3/library/stdtypes.html?highlight=endswith#str.endswith +[and]: https://realpython.com/python-and-operator/ diff --git a/exercises/practice/bob/.approaches/if-statements/snippet.txt b/exercises/practice/bob/.approaches/if-statements/snippet.txt new file mode 100644 index 0000000000..0e166bed30 --- /dev/null +++ b/exercises/practice/bob/.approaches/if-statements/snippet.txt @@ -0,0 +1,7 @@ +if isShout and isQuestion: + return "Calm down, I know what I'm doing!" +if isShout: + return 'Whoa, chill out!' +if isQuestion: + return 'Sure.' +return 'Whatever.' diff --git a/exercises/practice/bob/.approaches/introduction.md b/exercises/practice/bob/.approaches/introduction.md new file mode 100644 index 0000000000..173bb46f65 --- /dev/null +++ b/exercises/practice/bob/.approaches/introduction.md @@ -0,0 +1,92 @@ +# Introduction + +There are various idiomatic approaches to solve Bob. +A basic approach can use a series of `if` statements to test the conditions. +The `if` statements could also be nested. +A list can contain answers from which the right response is selected by an index calculated from scores given to the conditions. + +## General guidance + +Regardless of the approach used, some things you could look out for include + +- If the input is stripped, [`rstrip`][rstrip] only once. + +- Use the [`endswith`][endswith] method instead of checking the last character by index for `?`. + +- Don't copy/paste the logic for determining a shout and for determing a question into determing a shouted question. + Combine the two determinations instead of copying them. + Not duplicating the code will keep the code [DRY][dry]. + +- Perhaps consider making `IsQuestion` and `IsShout` values set once instead of functions that are possibly called twice. + +- If an `if` statement can return, then an `elif` or `else` is not needed. + Execution will either return or will continue to the next statement anyway. + +## Approach: `if` statements + +```python +def response(hey_bob): + hey_bob = hey_bob.rstrip() + if not hey_bob: + return 'Fine. Be that way!' + isShout = hey_bob.isupper() + isQuestion = hey_bob.endswith('?') + if isShout and isQuestion: + return "Calm down, I know what I'm doing!" + if isShout: + return 'Whoa, chill out!' + if isQuestion: + return 'Sure.' + return 'Whatever.' + +``` + +For more information, check the [`if` statements approach][approach-if]. + +## Approach: `if` statements nested + +```python +def response(hey_bob): + hey_bob = hey_bob.rstrip() + if not hey_bob: + return 'Fine. Be that way!' + isShout = hey_bob.isupper() + isQuestion = hey_bob.endswith('?') + if isShout: + if isQuestion: + return "Calm down, I know what I'm doing!" + else: + return 'Whoa, chill out!' + if isQuestion: + return 'Sure.' + return 'Whatever.' + +``` + +For more information, check the [`if` statements nested approach][approach-if-nested]. + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +### Other approach: answer list + +A list can be defined that contains Bob’s answers, and each condition is given a score. +The correct answer is selected from the list by using the score as the list index. +For more information, check the [answer list approach][approach-answer-list]. + +## Which approach to use? + +The nested `if` approach is fastest, but some may consider it a bit less readable than the unnested `if` statements. +The answer list approach is slowest, but some may prefer doing away with the chain of `if` statements. +Since the difference between them is a few nanoseconds, which one is used may be a matter of stylistic preference. + +- To compare the performance of the approaches, check out the [Performance article][article-performance]. + +[rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip +[endswith]: https://docs.python.org/3/library/stdtypes.html?highlight=strip#str.endswith +[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself +[approach-if]: https://exercism.org/tracks/python/exercises/bob/approaches/if-statements +[approach-if-nested]: https://exercism.org/tracks/python/exercises/bob/approaches/if-statements-nested +[approach-answer-list]: https://exercism.org/tracks/python/exercises/bob/approaches/answer-list +[article-performance]: https://exercism.org/tracks/python/exercises/bob/articles/performance diff --git a/exercises/practice/bob/.articles/config.json b/exercises/practice/bob/.articles/config.json new file mode 100644 index 0000000000..0fa4e15775 --- /dev/null +++ b/exercises/practice/bob/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "7b04a95b-d56f-48d3-bb79-3523cfee44ad", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach for Bob's response.", + "authors": ["bobahop"] + } + ] +} diff --git a/exercises/practice/bob/.articles/performance/code/Benchmark.py b/exercises/practice/bob/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..b176dc90cc --- /dev/null +++ b/exercises/practice/bob/.articles/performance/code/Benchmark.py @@ -0,0 +1,62 @@ +import timeit + +loops = 1_000_000 + +val = timeit.timeit("""response("I really don't have anything to say.")""", + """ +def response(hey_bob): + hey_bob = hey_bob.rstrip() + if not hey_bob: + return 'Fine. Be that way!' + isShout = hey_bob.isupper() + isQuestion = hey_bob.endswith('?') + if isShout and isQuestion: + return "Calm down, I know what I'm doing!" + if isShout: + return 'Whoa, chill out!' + if isQuestion: + return 'Sure.' + return 'Whatever.' + +""", number=loops) / loops + +print(f"if statements: {val}") + + +val = timeit.timeit("""response("I really don't have anything to say.")""", + """ +def response(hey_bob): + hey_bob = hey_bob.rstrip() + if not hey_bob: + return 'Fine. Be that way!' + isShout = hey_bob.isupper() + isQuestion = hey_bob.endswith('?') + if isShout: + if isQuestion: + return "Calm down, I know what I'm doing!" + else: + return 'Whoa, chill out!' + if isQuestion: + return 'Sure.' + return 'Whatever.' + +""", number=loops) / loops + +print(f"if statements nested: {val}") + +val = timeit.timeit("""response("I really don't have anything to say.")""", + """ + +_ANSWERS = ["Whatever.", "Sure.", "Whoa, chill out!", "Calm down, I know what I'm doing!"] + +def response(hey_bob): + hey_bob = hey_bob.rstrip() + if not hey_bob: return 'Fine. Be that way!' + isShout = 2 if hey_bob.isupper() else 0 + isQuestion = 1 if hey_bob.endswith('?') else 0 + return _ANSWERS[isShout + isQuestion] + + +""", number=loops) / loops + +print(f"answer list: {val}") diff --git a/exercises/practice/bob/.articles/performance/content.md b/exercises/practice/bob/.articles/performance/content.md new file mode 100644 index 0000000000..c90c3688f6 --- /dev/null +++ b/exercises/practice/bob/.articles/performance/content.md @@ -0,0 +1,31 @@ +# Performance + +In this approach, we'll find out how to most efficiently determine the response for Bob in Python. + +The [approaches page][approaches] lists two idiomatic approaches to this exercise: + +1. [Using `if` statements][approach-if]. +2. [Using `if` statements nested][approach-if-nested]. + +For our performance investigation, we'll also include a third approach that [uses an answer list][approach-answer-list]. + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. + +``` +if statements: 2.4691750000056346e-07 +if statements nested: 2.3319310000078987e-07 +answer list: 2.5469370000064373e-07 +``` + +The nested `if` approach is fastest, but some may consider nested `if` statements a bit less readable than the unnested `if` statements. +The answer list approach is slowest, but some may prefer doing away with the chain of `if` statements. +Since the difference between them is a few nanoseconds, which one is used may be a matter of stylistic preference. + +[approaches]: https://exercism.org/tracks/python/exercises/bob/approaches +[approach-if]: https://exercism.org/tracks/python/exercises/bob/approaches/if-statements +[approach-if-nested]: https://exercism.org/tracks/python/exercises/bob/approaches/if-statements-nested +[approach-answer-list]: https://exercism.org/tracks/python/exercises/bob/approaches/answer-list +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/bob/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html diff --git a/exercises/practice/bob/.articles/performance/snippet.md b/exercises/practice/bob/.articles/performance/snippet.md new file mode 100644 index 0000000000..4735892eac --- /dev/null +++ b/exercises/practice/bob/.articles/performance/snippet.md @@ -0,0 +1,5 @@ +``` +if statements: 2.4691750000056346e-07 +if statements nested: 2.3319310000078987e-07 +answer array: 2.5469370000064373e-07 +``` From 1ce3e3b1a4711945db0feed5d0daf77fae3d47b2 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 16 Nov 2022 17:29:29 +0100 Subject: [PATCH 005/826] Sync exercise metadata with problem-specifications This syncs all the metadata for all the practice exercises with upstream changes in problem-specifications, in one fell swoop. The changes are all nitpicky changes made in order to ensure that source urls all use https rather than http, along with some tweaks to better describe the source of certain exercises. --- exercises/practice/affine-cipher/.meta/config.json | 2 +- exercises/practice/allergies/.meta/config.json | 4 ++-- exercises/practice/atbash-cipher/.meta/config.json | 2 +- exercises/practice/beer-song/.meta/config.json | 2 +- exercises/practice/binary-search/.meta/config.json | 2 +- exercises/practice/bob/.meta/config.json | 2 +- exercises/practice/book-store/.meta/config.json | 2 +- exercises/practice/bowling/.meta/config.json | 2 +- exercises/practice/circular-buffer/.meta/config.json | 2 +- exercises/practice/crypto-square/.meta/config.json | 2 +- exercises/practice/diamond/.meta/config.json | 2 +- exercises/practice/difference-of-squares/.meta/config.json | 2 +- exercises/practice/diffie-hellman/.meta/config.json | 2 +- exercises/practice/etl/.meta/config.json | 4 ++-- exercises/practice/food-chain/.meta/config.json | 2 +- exercises/practice/gigasecond/.meta/config.json | 2 +- exercises/practice/grains/.meta/config.json | 4 ++-- exercises/practice/grep/.meta/config.json | 2 +- exercises/practice/hamming/.meta/config.json | 2 +- exercises/practice/hello-world/.meta/config.json | 2 +- exercises/practice/house/.meta/config.json | 2 +- exercises/practice/kindergarten-garden/.meta/config.json | 4 ++-- exercises/practice/largest-series-product/.meta/config.json | 2 +- exercises/practice/leap/.meta/config.json | 4 ++-- exercises/practice/luhn/.meta/config.json | 2 +- exercises/practice/matrix/.meta/config.json | 4 ++-- exercises/practice/nth-prime/.meta/config.json | 2 +- exercises/practice/ocr-numbers/.meta/config.json | 2 +- exercises/practice/palindrome-products/.meta/config.json | 2 +- exercises/practice/perfect-numbers/.meta/config.json | 2 +- exercises/practice/phone-number/.meta/config.json | 4 ++-- exercises/practice/prime-factors/.meta/config.json | 2 +- exercises/practice/pythagorean-triplet/.meta/config.json | 2 +- exercises/practice/queen-attack/.meta/config.json | 2 +- exercises/practice/rna-transcription/.meta/config.json | 2 +- exercises/practice/roman-numerals/.meta/config.json | 2 +- exercises/practice/saddle-points/.meta/config.json | 2 +- exercises/practice/say/.meta/config.json | 4 ++-- exercises/practice/secret-handshake/.meta/config.json | 2 +- exercises/practice/series/.meta/config.json | 2 +- exercises/practice/sieve/.meta/config.json | 2 +- exercises/practice/simple-cipher/.meta/config.json | 2 +- exercises/practice/space-age/.meta/config.json | 2 +- exercises/practice/sum-of-multiples/.meta/config.json | 2 +- exercises/practice/triangle/.meta/config.json | 2 +- exercises/practice/twelve-days/.meta/config.json | 2 +- exercises/practice/two-bucket/.meta/config.json | 2 +- 47 files changed, 55 insertions(+), 55 deletions(-) diff --git a/exercises/practice/affine-cipher/.meta/config.json b/exercises/practice/affine-cipher/.meta/config.json index 6b813c41c9..fb6606be5d 100644 --- a/exercises/practice/affine-cipher/.meta/config.json +++ b/exercises/practice/affine-cipher/.meta/config.json @@ -21,5 +21,5 @@ ] }, "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Affine_cipher" + "source_url": "https://en.wikipedia.org/wiki/Affine_cipher" } diff --git a/exercises/practice/allergies/.meta/config.json b/exercises/practice/allergies/.meta/config.json index 7158e9f7aa..d38070cec6 100644 --- a/exercises/practice/allergies/.meta/config.json +++ b/exercises/practice/allergies/.meta/config.json @@ -30,6 +30,6 @@ ".meta/example.py" ] }, - "source": "Jumpstart Lab Warm-up", - "source_url": "http://jumpstartlab.com" + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu" } diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json index d52c99e2b8..a1c2467361 100644 --- a/exercises/practice/atbash-cipher/.meta/config.json +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -29,5 +29,5 @@ ] }, "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Atbash" + "source_url": "https://en.wikipedia.org/wiki/Atbash" } diff --git a/exercises/practice/beer-song/.meta/config.json b/exercises/practice/beer-song/.meta/config.json index fee3a8ff5a..a59b25cd4a 100644 --- a/exercises/practice/beer-song/.meta/config.json +++ b/exercises/practice/beer-song/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "Learn to Program by Chris Pine", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=06" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=06" } diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json index 43b9f0b134..cc3fb2823f 100644 --- a/exercises/practice/binary-search/.meta/config.json +++ b/exercises/practice/binary-search/.meta/config.json @@ -31,5 +31,5 @@ ] }, "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Binary_search_algorithm" + "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm" } diff --git a/exercises/practice/bob/.meta/config.json b/exercises/practice/bob/.meta/config.json index 2ff720e1e1..1c91b43760 100644 --- a/exercises/practice/bob/.meta/config.json +++ b/exercises/practice/bob/.meta/config.json @@ -43,5 +43,5 @@ ] }, "source": "Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=06" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=06" } diff --git a/exercises/practice/book-store/.meta/config.json b/exercises/practice/book-store/.meta/config.json index da8d70010d..ed1c0f5556 100644 --- a/exercises/practice/book-store/.meta/config.json +++ b/exercises/practice/book-store/.meta/config.json @@ -28,5 +28,5 @@ ] }, "source": "Inspired by the harry potter kata from Cyber-Dojo.", - "source_url": "http://cyber-dojo.org" + "source_url": "https://cyber-dojo.org" } diff --git a/exercises/practice/bowling/.meta/config.json b/exercises/practice/bowling/.meta/config.json index b52768b1d1..475fd3bf71 100644 --- a/exercises/practice/bowling/.meta/config.json +++ b/exercises/practice/bowling/.meta/config.json @@ -23,5 +23,5 @@ ] }, "source": "The Bowling Game Kata from UncleBob", - "source_url": "http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" + "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" } diff --git a/exercises/practice/circular-buffer/.meta/config.json b/exercises/practice/circular-buffer/.meta/config.json index f94e3c0864..ba40c18f70 100644 --- a/exercises/practice/circular-buffer/.meta/config.json +++ b/exercises/practice/circular-buffer/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Circular_buffer" + "source_url": "https://en.wikipedia.org/wiki/Circular_buffer" } diff --git a/exercises/practice/crypto-square/.meta/config.json b/exercises/practice/crypto-square/.meta/config.json index 69b29a4745..c8e34bd051 100644 --- a/exercises/practice/crypto-square/.meta/config.json +++ b/exercises/practice/crypto-square/.meta/config.json @@ -30,5 +30,5 @@ ] }, "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/diamond/.meta/config.json b/exercises/practice/diamond/.meta/config.json index 3e68c65ffd..0108dfaa9b 100644 --- a/exercises/practice/diamond/.meta/config.json +++ b/exercises/practice/diamond/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "Seb Rose", - "source_url": "http://claysnow.co.uk/recycling-tests-in-tdd/" + "source_url": "https://web.archive.org/web/20220807163751/http://claysnow.co.uk/recycling-tests-in-tdd/" } diff --git a/exercises/practice/difference-of-squares/.meta/config.json b/exercises/practice/difference-of-squares/.meta/config.json index a0cfedf099..3dc17de673 100644 --- a/exercises/practice/difference-of-squares/.meta/config.json +++ b/exercises/practice/difference-of-squares/.meta/config.json @@ -28,5 +28,5 @@ ] }, "source": "Problem 6 at Project Euler", - "source_url": "http://projecteuler.net/problem=6" + "source_url": "https://projecteuler.net/problem=6" } diff --git a/exercises/practice/diffie-hellman/.meta/config.json b/exercises/practice/diffie-hellman/.meta/config.json index 5ae726f56f..feb9623d4e 100644 --- a/exercises/practice/diffie-hellman/.meta/config.json +++ b/exercises/practice/diffie-hellman/.meta/config.json @@ -21,5 +21,5 @@ ] }, "source": "Wikipedia, 1024 bit key from www.cryptopp.com/wiki.", - "source_url": "http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" + "source_url": "https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" } diff --git a/exercises/practice/etl/.meta/config.json b/exercises/practice/etl/.meta/config.json index 28aa9acb8e..777e51cb84 100644 --- a/exercises/practice/etl/.meta/config.json +++ b/exercises/practice/etl/.meta/config.json @@ -28,6 +28,6 @@ ".meta/example.py" ] }, - "source": "The Jumpstart Lab team", - "source_url": "http://jumpstartlab.com" + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu" } diff --git a/exercises/practice/food-chain/.meta/config.json b/exercises/practice/food-chain/.meta/config.json index 1c71a17e29..1f359e9b0e 100644 --- a/exercises/practice/food-chain/.meta/config.json +++ b/exercises/practice/food-chain/.meta/config.json @@ -22,5 +22,5 @@ ] }, "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/There_Was_an_Old_Lady_Who_Swallowed_a_Fly" + "source_url": "https://en.wikipedia.org/wiki/There_Was_an_Old_Lady_Who_Swallowed_a_Fly" } diff --git a/exercises/practice/gigasecond/.meta/config.json b/exercises/practice/gigasecond/.meta/config.json index 5ef0e8680f..b0af657f37 100644 --- a/exercises/practice/gigasecond/.meta/config.json +++ b/exercises/practice/gigasecond/.meta/config.json @@ -30,5 +30,5 @@ ] }, "source": "Chapter 9 in Chris Pine's online Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=09" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=09" } diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json index 55ec49f0b7..12b2a81dca 100644 --- a/exercises/practice/grains/.meta/config.json +++ b/exercises/practice/grains/.meta/config.json @@ -29,6 +29,6 @@ ".meta/example.py" ] }, - "source": "JavaRanch Cattle Drive, exercise 6", - "source_url": "http://www.javaranch.com/grains.jsp" + "source": "The CodeRanch Cattle Drive, Assignment 6", + "source_url": "https://coderanch.com/wiki/718824/Grains" } diff --git a/exercises/practice/grep/.meta/config.json b/exercises/practice/grep/.meta/config.json index 082620a848..57c0ec8e07 100644 --- a/exercises/practice/grep/.meta/config.json +++ b/exercises/practice/grep/.meta/config.json @@ -27,5 +27,5 @@ ] }, "source": "Conversation with Nate Foster.", - "source_url": "http://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf" + "source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf" } diff --git a/exercises/practice/hamming/.meta/config.json b/exercises/practice/hamming/.meta/config.json index 13e6ee6fc2..34b97637ba 100644 --- a/exercises/practice/hamming/.meta/config.json +++ b/exercises/practice/hamming/.meta/config.json @@ -33,5 +33,5 @@ ] }, "source": "The Calculating Point Mutations problem at Rosalind", - "source_url": "http://rosalind.info/problems/hamm/" + "source_url": "https://rosalind.info/problems/hamm/" } diff --git a/exercises/practice/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json index e2bdf55bc6..c3520cfdee 100644 --- a/exercises/practice/hello-world/.meta/config.json +++ b/exercises/practice/hello-world/.meta/config.json @@ -29,5 +29,5 @@ ] }, "source": "This is an exercise to introduce users to using Exercism", - "source_url": "http://en.wikipedia.org/wiki/%22Hello,_world!%22_program" + "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" } diff --git a/exercises/practice/house/.meta/config.json b/exercises/practice/house/.meta/config.json index e18b4ea585..7de79da60f 100644 --- a/exercises/practice/house/.meta/config.json +++ b/exercises/practice/house/.meta/config.json @@ -27,5 +27,5 @@ ] }, "source": "British nursery rhyme", - "source_url": "http://en.wikipedia.org/wiki/This_Is_The_House_That_Jack_Built" + "source_url": "https://en.wikipedia.org/wiki/This_Is_The_House_That_Jack_Built" } diff --git a/exercises/practice/kindergarten-garden/.meta/config.json b/exercises/practice/kindergarten-garden/.meta/config.json index f10fda1c5c..396235f79d 100644 --- a/exercises/practice/kindergarten-garden/.meta/config.json +++ b/exercises/practice/kindergarten-garden/.meta/config.json @@ -27,6 +27,6 @@ ".meta/example.py" ] }, - "source": "Random musings during airplane trip.", - "source_url": "http://jumpstartlab.com" + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu" } diff --git a/exercises/practice/largest-series-product/.meta/config.json b/exercises/practice/largest-series-product/.meta/config.json index b49cdb4cc0..8b12e5b55c 100644 --- a/exercises/practice/largest-series-product/.meta/config.json +++ b/exercises/practice/largest-series-product/.meta/config.json @@ -29,5 +29,5 @@ ] }, "source": "A variation on Problem 8 at Project Euler", - "source_url": "http://projecteuler.net/problem=8" + "source_url": "https://projecteuler.net/problem=8" } diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index b1f9cbff29..687638ae0b 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -33,6 +33,6 @@ ".meta/example.py" ] }, - "source": "JavaRanch Cattle Drive, exercise 3", - "source_url": "http://www.javaranch.com/leap.jsp" + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" } diff --git a/exercises/practice/luhn/.meta/config.json b/exercises/practice/luhn/.meta/config.json index 24aa17c97d..ead518b8e1 100644 --- a/exercises/practice/luhn/.meta/config.json +++ b/exercises/practice/luhn/.meta/config.json @@ -35,5 +35,5 @@ ] }, "source": "The Luhn Algorithm on Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Luhn_algorithm" + "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm" } diff --git a/exercises/practice/matrix/.meta/config.json b/exercises/practice/matrix/.meta/config.json index 63c7af0049..5b9aaf85ba 100644 --- a/exercises/practice/matrix/.meta/config.json +++ b/exercises/practice/matrix/.meta/config.json @@ -28,6 +28,6 @@ ".meta/example.py" ] }, - "source": "Warmup to the `saddle-points` warmup.", - "source_url": "http://jumpstartlab.com" + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu" } diff --git a/exercises/practice/nth-prime/.meta/config.json b/exercises/practice/nth-prime/.meta/config.json index 46b004d8b6..c38d07e93a 100644 --- a/exercises/practice/nth-prime/.meta/config.json +++ b/exercises/practice/nth-prime/.meta/config.json @@ -30,5 +30,5 @@ ] }, "source": "A variation on Problem 7 at Project Euler", - "source_url": "http://projecteuler.net/problem=7" + "source_url": "https://projecteuler.net/problem=7" } diff --git a/exercises/practice/ocr-numbers/.meta/config.json b/exercises/practice/ocr-numbers/.meta/config.json index 52e7f23479..669baa24a0 100644 --- a/exercises/practice/ocr-numbers/.meta/config.json +++ b/exercises/practice/ocr-numbers/.meta/config.json @@ -29,5 +29,5 @@ ] }, "source": "Inspired by the Bank OCR kata", - "source_url": "http://codingdojo.org/cgi-bin/wiki.pl?KataBankOCR" + "source_url": "https://codingdojo.org/kata/BankOCR/" } diff --git a/exercises/practice/palindrome-products/.meta/config.json b/exercises/practice/palindrome-products/.meta/config.json index d05e4708b6..936f9e8b92 100644 --- a/exercises/practice/palindrome-products/.meta/config.json +++ b/exercises/practice/palindrome-products/.meta/config.json @@ -31,5 +31,5 @@ ] }, "source": "Problem 4 at Project Euler", - "source_url": "http://projecteuler.net/problem=4" + "source_url": "https://projecteuler.net/problem=4" } diff --git a/exercises/practice/perfect-numbers/.meta/config.json b/exercises/practice/perfect-numbers/.meta/config.json index 1e5021db2d..208b5094ec 100644 --- a/exercises/practice/perfect-numbers/.meta/config.json +++ b/exercises/practice/perfect-numbers/.meta/config.json @@ -27,5 +27,5 @@ ] }, "source": "Taken from Chapter 2 of Functional Thinking by Neal Ford.", - "source_url": "http://shop.oreilly.com/product/0636920029687.do" + "source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/" } diff --git a/exercises/practice/phone-number/.meta/config.json b/exercises/practice/phone-number/.meta/config.json index f05c4f8978..db2af4d694 100644 --- a/exercises/practice/phone-number/.meta/config.json +++ b/exercises/practice/phone-number/.meta/config.json @@ -29,6 +29,6 @@ ".meta/example.py" ] }, - "source": "Event Manager by JumpstartLab", - "source_url": "http://tutorials.jumpstartlab.com/projects/eventmanager.html" + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu" } diff --git a/exercises/practice/prime-factors/.meta/config.json b/exercises/practice/prime-factors/.meta/config.json index bb2f524650..1d89a38d6b 100644 --- a/exercises/practice/prime-factors/.meta/config.json +++ b/exercises/practice/prime-factors/.meta/config.json @@ -28,5 +28,5 @@ ] }, "source": "The Prime Factors Kata by Uncle Bob", - "source_url": "http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" + "source_url": "https://web.archive.org/web/20221026171801/http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" } diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 08a7aa50b8..209e162326 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -33,5 +33,5 @@ ] }, "source": "Problem 9 at Project Euler", - "source_url": "http://projecteuler.net/problem=9" + "source_url": "https://projecteuler.net/problem=9" } diff --git a/exercises/practice/queen-attack/.meta/config.json b/exercises/practice/queen-attack/.meta/config.json index c906de479d..a4558a4e5a 100644 --- a/exercises/practice/queen-attack/.meta/config.json +++ b/exercises/practice/queen-attack/.meta/config.json @@ -33,5 +33,5 @@ ] }, "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index 5f26e6c0b1..b310b8c036 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -34,5 +34,5 @@ ] }, "source": "Hyperphysics", - "source_url": "http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" + "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" } diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index 108aa29576..ea6611e8fb 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -28,5 +28,5 @@ ] }, "source": "The Roman Numeral Kata", - "source_url": "http://codingdojo.org/cgi-bin/index.pl?KataRomanNumerals" + "source_url": "https://codingdojo.org/kata/RomanNumerals/" } diff --git a/exercises/practice/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json index 81ef0e53f0..7deb5f4aaf 100644 --- a/exercises/practice/saddle-points/.meta/config.json +++ b/exercises/practice/saddle-points/.meta/config.json @@ -36,5 +36,5 @@ ] }, "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/say/.meta/config.json b/exercises/practice/say/.meta/config.json index 7a95c43e9c..506533a113 100644 --- a/exercises/practice/say/.meta/config.json +++ b/exercises/practice/say/.meta/config.json @@ -29,6 +29,6 @@ ".meta/example.py" ] }, - "source": "A variation on JavaRanch CattleDrive, exercise 4a", - "source_url": "http://www.javaranch.com/say.jsp" + "source": "A variation on the JavaRanch CattleDrive, Assignment 4", + "source_url": "https://coderanch.com/wiki/718804" } diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json index 1f9dc801d9..ee9aeb52a1 100644 --- a/exercises/practice/secret-handshake/.meta/config.json +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -31,5 +31,5 @@ ] }, "source": "Bert, in Mary Poppins", - "source_url": "http://www.imdb.com/title/tt0058331/quotes/qt0437047" + "source_url": "https://www.imdb.com/title/tt0058331/quotes/qt0437047" } diff --git a/exercises/practice/series/.meta/config.json b/exercises/practice/series/.meta/config.json index afa21de3ac..9953afd602 100644 --- a/exercises/practice/series/.meta/config.json +++ b/exercises/practice/series/.meta/config.json @@ -28,5 +28,5 @@ ] }, "source": "A subset of the Problem 8 at Project Euler", - "source_url": "http://projecteuler.net/problem=8" + "source_url": "https://projecteuler.net/problem=8" } diff --git a/exercises/practice/sieve/.meta/config.json b/exercises/practice/sieve/.meta/config.json index 52af39b8d3..ccf9424987 100644 --- a/exercises/practice/sieve/.meta/config.json +++ b/exercises/practice/sieve/.meta/config.json @@ -30,5 +30,5 @@ ] }, "source": "Sieve of Eratosthenes at Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" + "source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" } diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index fb95445dbb..2176f407c5 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -32,5 +32,5 @@ ] }, "source": "Substitution Cipher at Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Substitution_cipher" + "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" } diff --git a/exercises/practice/space-age/.meta/config.json b/exercises/practice/space-age/.meta/config.json index 23b8e4f59b..738e0fe39e 100644 --- a/exercises/practice/space-age/.meta/config.json +++ b/exercises/practice/space-age/.meta/config.json @@ -29,5 +29,5 @@ ] }, "source": "Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=01" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=01" } diff --git a/exercises/practice/sum-of-multiples/.meta/config.json b/exercises/practice/sum-of-multiples/.meta/config.json index 86acf2c0b6..bc2eba2a1c 100644 --- a/exercises/practice/sum-of-multiples/.meta/config.json +++ b/exercises/practice/sum-of-multiples/.meta/config.json @@ -32,5 +32,5 @@ ] }, "source": "A variation on Problem 1 at Project Euler", - "source_url": "http://projecteuler.net/problem=1" + "source_url": "https://projecteuler.net/problem=1" } diff --git a/exercises/practice/triangle/.meta/config.json b/exercises/practice/triangle/.meta/config.json index 87bcc3d9ba..d5264660fe 100644 --- a/exercises/practice/triangle/.meta/config.json +++ b/exercises/practice/triangle/.meta/config.json @@ -32,5 +32,5 @@ ] }, "source": "The Ruby Koans triangle project, parts 1 & 2", - "source_url": "http://rubykoans.com" + "source_url": "https://web.archive.org/web/20220831105330/http://rubykoans.com" } diff --git a/exercises/practice/twelve-days/.meta/config.json b/exercises/practice/twelve-days/.meta/config.json index d7c8471248..2ed3689f68 100644 --- a/exercises/practice/twelve-days/.meta/config.json +++ b/exercises/practice/twelve-days/.meta/config.json @@ -27,5 +27,5 @@ ] }, "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/The_Twelve_Days_of_Christmas_(song)" + "source_url": "https://en.wikipedia.org/wiki/The_Twelve_Days_of_Christmas_(song)" } diff --git a/exercises/practice/two-bucket/.meta/config.json b/exercises/practice/two-bucket/.meta/config.json index c70e317cb0..177aedc4e4 100644 --- a/exercises/practice/two-bucket/.meta/config.json +++ b/exercises/practice/two-bucket/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "Water Pouring Problem", - "source_url": "http://demonstrations.wolfram.com/WaterPouringProblem/" + "source_url": "https://demonstrations.wolfram.com/WaterPouringProblem/" } From a91777d7d860cd8c356780bba5aaab33b20aab3a Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 04:05:24 -0600 Subject: [PATCH 006/826] Bob approaches fixes (#3201) Final isShout and isQuestion remnants changed to is_shout and is_question. * Update introduction.md * Update snippet.txt * Update content.md * Update snippet.txt * Update content.md * Update snippet.txt * Update content.md * Update content.md * Update Benchmark.py * Update content.md --- .../bob/.approaches/answer-list/content.md | 37 ++++++++++--------- .../bob/.approaches/answer-list/snippet.txt | 8 ++-- .../if-statements-nested/content.md | 10 ++--- .../if-statements-nested/snippet.txt | 6 +-- .../bob/.approaches/if-statements/content.md | 10 ++--- .../bob/.approaches/if-statements/snippet.txt | 6 +-- .../practice/bob/.approaches/introduction.md | 22 +++++------ .../.articles/performance/code/Benchmark.py | 35 ++++++++++-------- 8 files changed, 70 insertions(+), 64 deletions(-) diff --git a/exercises/practice/bob/.approaches/answer-list/content.md b/exercises/practice/bob/.approaches/answer-list/content.md index de2049a7ad..b3894780e5 100644 --- a/exercises/practice/bob/.approaches/answer-list/content.md +++ b/exercises/practice/bob/.approaches/answer-list/content.md @@ -1,7 +1,7 @@ # Answer list ```python -_ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!', +ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!', "Calm down, I know what I'm doing!"] @@ -9,9 +9,9 @@ def response(hey_bob): hey_bob = hey_bob.rstrip() if not hey_bob: return 'Fine. Be that way!' - isShout = (2 if hey_bob.isupper() else 0) - isQuestion = (1 if hey_bob.endswith('?') else 0) - return _ANSWERS[isShout + isQuestion] + is_shout = 2 if hey_bob.isupper() else 0 + is_question = 1 if hey_bob.endswith('?') else 0 + return ANSWERS[is_shout + is_question] ``` @@ -19,11 +19,15 @@ In this approach you define a [list][list] that contains Bob’s answers, and ea The correct answer is selected from the list by using the score as the list index. Python doesn't _enforce_ having real constant values, -but the `_ANSWERS` list is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. +but the `ANSWERS` list is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. It indicates that the value is not intended to be changed. -Python also does not have real [private][private] variables, -but a leading underscore is the naming convention for indicating that a variable is not meant to be part of the public API. -It should be considered an implementation detail and subject to change without notice. + +```exercism/note +`ANSWERS` could prevent item reassignment by being defined as a [tuple](https://realpython.com/python-lists-tuples/#python-tuples) instead of a list. +The items in a tuple cannot be changed, and the performance between a tuple and a list here is equivalent. +The entire `ANSWERS` tuple could still be reassigned to another tuple, +so uppercase letters would still be used to indicate that the `ANSWERS` tuple should not be changed. +``` The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input. If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing. @@ -39,25 +43,24 @@ For example, `?` and `3` are not cased characters, as they do not change between `a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase. ``` -If `isupper` is `True`, then `isShout` is given the value of `2`; otherwise, it is given the value of `0`. +If `isupper` is `True`, then `is_shout` is given the value of `2`; otherwise, it is given the value of `0`. The [`endswith`][endswith] method is used to determine if the input ends with a question mark. -If the test for `endswith('?')` is `True`, then `isQuestion` is given the value of `1`; otherwise it is given the value of `0`. +If the test for `endswith('?')` is `True`, then `is_question` is given the value of `1`; otherwise it is given the value of `0`. The response is selected from the list by the index like so -| isShout | isQuestion | Index | Answer | -| ------- | ---------- | --------- | ------------------------------------- | -| `false` | `false` | 0 + 0 = 0 | `"Whatever."` | -| `false` | `true` | 0 + 1 = 1 | `"Sure."` | -| `true` | `false` | 2 + 0 = 2 | `"Whoa, chill out!"` | -| `true` | `true` | 2 + 1 = 3 | `"Calm down, I know what I'm doing!"` | +| is_shout | is_question | Index | Answer | +| -------- | ----------- | --------- | ------------------------------------- | +| `false` | `false` | 0 + 0 = 0 | `"Whatever."` | +| `false` | `true` | 0 + 1 = 1 | `"Sure."` | +| `true` | `false` | 2 + 0 = 2 | `"Whoa, chill out!"` | +| `true` | `true` | 2 + 1 = 3 | `"Calm down, I know what I'm doing!"` | [list]: https://docs.python.org/3/library/stdtypes.html?highlight=list#list [const]: https://realpython.com/python-constants/ -[private]: https://docs.python.org/3/tutorial/classes.html#private-variables [rstrip]: https://docs.python.org/3/library/stdtypes.html?highlight=rstrip#str.rstrip [falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ [not]: https://docs.python.org/3/reference/expressions.html#not diff --git a/exercises/practice/bob/.approaches/answer-list/snippet.txt b/exercises/practice/bob/.approaches/answer-list/snippet.txt index 0d834fa271..b1ce322f72 100644 --- a/exercises/practice/bob/.approaches/answer-list/snippet.txt +++ b/exercises/practice/bob/.approaches/answer-list/snippet.txt @@ -1,6 +1,6 @@ -_ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!', +ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!', "Calm down, I know what I'm doing!"] # code snipped -isShout = (2 if hey_bob.isupper() else 0) -isQuestion = (1 if hey_bob.endswith('?') else 0) -return _ANSWERS[isShout + isQuestion] +is_shout = 2 if hey_bob.isupper() else 0 +is_question = 1 if hey_bob.endswith('?') else 0 +return ANSWERS[is_shout + is_question] diff --git a/exercises/practice/bob/.approaches/if-statements-nested/content.md b/exercises/practice/bob/.approaches/if-statements-nested/content.md index e070216986..06b5ed527c 100644 --- a/exercises/practice/bob/.approaches/if-statements-nested/content.md +++ b/exercises/practice/bob/.approaches/if-statements-nested/content.md @@ -5,14 +5,14 @@ def response(hey_bob): hey_bob = hey_bob.rstrip() if not hey_bob: return 'Fine. Be that way!' - isShout = hey_bob.isupper() - isQuestion = hey_bob.endswith('?') - if isShout: - if isQuestion: + is_shout = hey_bob.isupper() + is_question = hey_bob.endswith('?') + if is_shout: + if is_question: return "Calm down, I know what I'm doing!" else: return 'Whoa, chill out!' - if isQuestion: + if is_question: return 'Sure.' return 'Whatever.' diff --git a/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt b/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt index c86b57e682..0df6c2fe9e 100644 --- a/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt +++ b/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt @@ -1,8 +1,8 @@ -if isShout: - if isQuestion: +if is_shout: + if is_question: return "Calm down, I know what I'm doing!" else: return 'Whoa, chill out!' -if isQuestion: +if is_question: return 'Sure.' return 'Whatever.' diff --git a/exercises/practice/bob/.approaches/if-statements/content.md b/exercises/practice/bob/.approaches/if-statements/content.md index 8bae511649..7d4d84e368 100644 --- a/exercises/practice/bob/.approaches/if-statements/content.md +++ b/exercises/practice/bob/.approaches/if-statements/content.md @@ -5,13 +5,13 @@ def response(hey_bob): hey_bob = hey_bob.rstrip() if not hey_bob: return 'Fine. Be that way!' - isShout = hey_bob.isupper() - isQuestion = hey_bob.endswith('?') - if isShout and isQuestion: + is_shout = hey_bob.isupper() + is_question = hey_bob.endswith('?') + if is_shout and is_question: return "Calm down, I know what I'm doing!" - if isShout: + if is_shout: return 'Whoa, chill out!' - if isQuestion: + if is_question: return 'Sure.' return 'Whatever.' diff --git a/exercises/practice/bob/.approaches/if-statements/snippet.txt b/exercises/practice/bob/.approaches/if-statements/snippet.txt index 0e166bed30..1ffdaf7799 100644 --- a/exercises/practice/bob/.approaches/if-statements/snippet.txt +++ b/exercises/practice/bob/.approaches/if-statements/snippet.txt @@ -1,7 +1,7 @@ -if isShout and isQuestion: +if is_shout and is_question: return "Calm down, I know what I'm doing!" -if isShout: +if is_shout: return 'Whoa, chill out!' -if isQuestion: +if is_question: return 'Sure.' return 'Whatever.' diff --git a/exercises/practice/bob/.approaches/introduction.md b/exercises/practice/bob/.approaches/introduction.md index 173bb46f65..ccdfa93f90 100644 --- a/exercises/practice/bob/.approaches/introduction.md +++ b/exercises/practice/bob/.approaches/introduction.md @@ -17,7 +17,7 @@ Regardless of the approach used, some things you could look out for include Combine the two determinations instead of copying them. Not duplicating the code will keep the code [DRY][dry]. -- Perhaps consider making `IsQuestion` and `IsShout` values set once instead of functions that are possibly called twice. +- Perhaps consider making `is_question` and `is_shout` values set once instead of functions that are possibly called twice. - If an `if` statement can return, then an `elif` or `else` is not needed. Execution will either return or will continue to the next statement anyway. @@ -29,13 +29,13 @@ def response(hey_bob): hey_bob = hey_bob.rstrip() if not hey_bob: return 'Fine. Be that way!' - isShout = hey_bob.isupper() - isQuestion = hey_bob.endswith('?') - if isShout and isQuestion: + is_shout = hey_bob.isupper() + is_question = hey_bob.endswith('?') + if is_shout and is_question: return "Calm down, I know what I'm doing!" - if isShout: + if is_shout: return 'Whoa, chill out!' - if isQuestion: + if is_question: return 'Sure.' return 'Whatever.' @@ -50,14 +50,14 @@ def response(hey_bob): hey_bob = hey_bob.rstrip() if not hey_bob: return 'Fine. Be that way!' - isShout = hey_bob.isupper() - isQuestion = hey_bob.endswith('?') - if isShout: - if isQuestion: + is_shout = hey_bob.isupper() + is_question = hey_bob.endswith('?') + if is_shout: + if is_question: return "Calm down, I know what I'm doing!" else: return 'Whoa, chill out!' - if isQuestion: + if is_question: return 'Sure.' return 'Whatever.' diff --git a/exercises/practice/bob/.articles/performance/code/Benchmark.py b/exercises/practice/bob/.articles/performance/code/Benchmark.py index b176dc90cc..e726ed1953 100644 --- a/exercises/practice/bob/.articles/performance/code/Benchmark.py +++ b/exercises/practice/bob/.articles/performance/code/Benchmark.py @@ -8,13 +8,13 @@ def response(hey_bob): hey_bob = hey_bob.rstrip() if not hey_bob: return 'Fine. Be that way!' - isShout = hey_bob.isupper() - isQuestion = hey_bob.endswith('?') - if isShout and isQuestion: + is_shout = hey_bob.isupper() + is_question = hey_bob.endswith('?') + if is_shout and is_question: return "Calm down, I know what I'm doing!" - if isShout: + if is_shout: return 'Whoa, chill out!' - if isQuestion: + if is_question: return 'Sure.' return 'Whatever.' @@ -29,14 +29,14 @@ def response(hey_bob): hey_bob = hey_bob.rstrip() if not hey_bob: return 'Fine. Be that way!' - isShout = hey_bob.isupper() - isQuestion = hey_bob.endswith('?') - if isShout: - if isQuestion: + is_shout = hey_bob.isupper() + is_question = hey_bob.endswith('?') + if is_shout: + if is_question: return "Calm down, I know what I'm doing!" else: - return 'Whoa, chill out!' - if isQuestion: + return 'Whoa, chill out!' + if is_question: return 'Sure.' return 'Whatever.' @@ -47,14 +47,17 @@ def response(hey_bob): val = timeit.timeit("""response("I really don't have anything to say.")""", """ -_ANSWERS = ["Whatever.", "Sure.", "Whoa, chill out!", "Calm down, I know what I'm doing!"] +ANSWERS = ['Whatever.', 'Sure.', 'Whoa, chill out!', + "Calm down, I know what I'm doing!"] + def response(hey_bob): hey_bob = hey_bob.rstrip() - if not hey_bob: return 'Fine. Be that way!' - isShout = 2 if hey_bob.isupper() else 0 - isQuestion = 1 if hey_bob.endswith('?') else 0 - return _ANSWERS[isShout + isQuestion] + if not hey_bob: + return 'Fine. Be that way!' + is_shout = 2 if hey_bob.isupper() else 0 + is_question = 1 if hey_bob.endswith('?') else 0 + return ANSWERS[is_shout + is_question] """, number=loops) / loops From b6362043e86d7585149866bd29be1b5498e5539e Mon Sep 17 00:00:00 2001 From: Exercism Bot <66069679+exercism-bot@users.noreply.github.com> Date: Thu, 17 Nov 2022 13:28:43 +0000 Subject: [PATCH 007/826] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/ceface8fc48f8f8be135cecafe5ea8355aac9ad6 --- .github/labels.yml | 10 +++---- CODE_OF_CONDUCT.md | 57 ++++++++++++++++++++--------------- bin/fetch-configlet | 72 +++++++++++++++++++++++++++------------------ 3 files changed, 83 insertions(+), 56 deletions(-) diff --git a/.github/labels.yml b/.github/labels.yml index c76eadfd19..3f8780530d 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -157,16 +157,16 @@ description: "Work on Documentation" color: "ffffff" -# This label can be added to accept PRs as part of Hacktoberfest -- name: "hacktoberfest-accepted" - description: "Make this PR count for hacktoberfest" - color: "ff7518" - # This Exercism-wide label is added to all automatically created pull requests that help migrate/prepare a track for Exercism v3 - name: "v3-migration 🤖" description: "Preparing for Exercism v3" color: "e99695" +# This Exercism-wide label can be used to bulk-close issues in preparation for pausing community contributions +- name: "paused" + description: "Work paused until further notice" + color: "e4e669" + # ----------------------------------------------------------------------------------------- # # These are the repository-specific labels that augment the Exercise-wide labels defined in # # https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml. # diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 9bb22baa71..df8e36761c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,17 +2,23 @@ ## Introduction -Exercism is a platform centered around empathetic conversation. We have a low tolerance for communication that makes anyone feel unwelcome, unsupported, insulted or discriminated against. +Exercism is a platform centered around empathetic conversation. +We have a low tolerance for communication that makes anyone feel unwelcome, unsupported, insulted or discriminated against. ## Seen or experienced something uncomfortable? -If you see or experience abuse, harassment, discrimination, or feel unsafe or upset, please email [abuse@exercism.org](mailto:abuse@exercism.org?subject=%5BCoC%5D) and include \[CoC\] in the subject line. We will follow up with you as a priority. +If you see or experience abuse, harassment, discrimination, or feel unsafe or upset, please email [abuse@exercism.org](mailto:abuse@exercism.org?subject=%5BCoC%5D) and include \[CoC\] in the subject line. +We will follow up with you as a priority. ## Enforcement -We actively monitor for Code of Conduct (CoC) violations and take any reports of violations extremely seriously. We have banned contributors, mentors and users due to violations. +We actively monitor for Code of Conduct (CoC) violations and take any reports of violations extremely seriously. +We have banned contributors, mentors and users due to violations. -After we receive a report of a CoC violation, we view that person's conversation history on Exercism and related communication channels and attempt to understand whether someone has deliberately broken the CoC, or accidentally crossed a line. We generally reach out to the person who has been reported to discuss any concerns we have and warn them that repeated violations will result in a ban. Sometimes we decide that no violation has occurred and that no action is required and sometimes we will also ban people on a first offense. We strive to be fair, but will err on the side of protecting the culture of our community. +After we receive a report of a CoC violation, we view that person's conversation history on Exercism and related communication channels and attempt to understand whether someone has deliberately broken the CoC, or accidentally crossed a line. +We generally reach out to the person who has been reported to discuss any concerns we have and warn them that repeated violations will result in a ban. +Sometimes we decide that no violation has occurred and that no action is required and sometimes we will also ban people on a first offense. +We strive to be fair, but will err on the side of protecting the culture of our community. Exercism's leadership reserve the right to take whatever action they feel appropriate with regards to CoC violations. @@ -36,15 +42,16 @@ Exercism should be a safe place for everybody regardless of - Race - Age - Religion -- Anything else you can think of. +- Anything else you can think of As someone who is part of this community, you agree that: -- We are collectively and individually committed to safety and inclusivity. -- We have zero tolerance for abuse, harassment, or discrimination. -- We respect people’s boundaries and identities. -- We refrain from using language that can be considered offensive or oppressive (systemically or otherwise), eg. sexist, racist, homophobic, transphobic, ableist, classist, etc. - this includes (but is not limited to) various slurs. -- We avoid using offensive topics as a form of humor. +- We are collectively and individually committed to safety and inclusivity +- We have zero tolerance for abuse, harassment, or discrimination +- We respect people’s boundaries and identities +- We refrain from using language that can be considered offensive or oppressive (systemically or otherwise), eg. sexist, racist, homophobic, transphobic, ableist, classist, etc. + - this includes (but is not limited to) various slurs. +- We avoid using offensive topics as a form of humor We actively work towards: @@ -57,26 +64,30 @@ We condemn: - Stalking, doxxing, or publishing private information - Violence, threats of violence or violent language - Anything that compromises people’s safety -- Conduct or speech which might be considered sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory or offensive in nature. -- The use of unwelcome, suggestive, derogatory or inappropriate nicknames or terms. -- Disrespect towards others (jokes, innuendo, dismissive attitudes) and towards differences of opinion. -- Intimidation or harassment (online or in-person). Please read the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md) for how we interpret harassment. -- Inappropriate attention or contact. -- Not understanding the differences between constructive criticism and disparagement. +- Conduct or speech which might be considered sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory or offensive in nature +- The use of unwelcome, suggestive, derogatory or inappropriate nicknames or terms +- Disrespect towards others (jokes, innuendo, dismissive attitudes) and towards differences of opinion +- Intimidation or harassment (online or in-person). + Please read the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md) for how we interpret harassment +- Inappropriate attention or contact +- Not understanding the differences between constructive criticism and disparagement These things are NOT OK. -Be aware of how your actions affect others. If it makes someone uncomfortable, stop. +Be aware of how your actions affect others. +If it makes someone uncomfortable, stop. If you say something that is found offensive, and you are called out on it, try to: -- Listen without interruption. -- Believe what the person is saying & do not attempt to disqualify what they have to say. -- Ask for tips / help with avoiding making the offense in the future. -- Apologize and ask forgiveness. +- Listen without interruption +- Believe what the person is saying & do not attempt to disqualify what they have to say +- Ask for tips / help with avoiding making the offense in the future +- Apologize and ask forgiveness ## History -This policy was initially adopted from the Front-end London Slack community and has been modified since. A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). +This policy was initially adopted from the Front-end London Slack community and has been modified since. +A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). -_This policy is a "living" document, and subject to refinement and expansion in the future. This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._ +_This policy is a "living" document, and subject to refinement and expansion in the future. +This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._ diff --git a/bin/fetch-configlet b/bin/fetch-configlet index 827088ab17..4800e15084 100755 --- a/bin/fetch-configlet +++ b/bin/fetch-configlet @@ -6,29 +6,6 @@ set -eo pipefail -readonly LATEST='https://api.github.com/repos/exercism/configlet/releases/latest' - -case "$(uname)" in - Darwin*) os='mac' ;; - Linux*) os='linux' ;; - Windows*) os='windows' ;; - MINGW*) os='windows' ;; - MSYS_NT-*) os='windows' ;; - *) os='linux' ;; -esac - -case "${os}" in - windows*) ext='zip' ;; - *) ext='tgz' ;; -esac - -case "$(uname -m)" in - *64*) arch='64bit' ;; - *686*) arch='32bit' ;; - *386*) arch='32bit' ;; - *) arch='64bit' ;; -esac - curlopts=( --silent --show-error @@ -41,15 +18,25 @@ if [[ -n "${GITHUB_TOKEN}" ]]; then curlopts+=(--header "authorization: Bearer ${GITHUB_TOKEN}") fi -suffix="${os}-${arch}.${ext}" - get_download_url() { - curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${LATEST}" | + local os="$1" + local ext="$2" + local latest='https://api.github.com/repos/exercism/configlet/releases/latest' + local arch + case "$(uname -m)" in + x86_64) arch='x86-64' ;; + *686*) arch='i386' ;; + *386*) arch='i386' ;; + *) arch='x86-64' ;; + esac + local suffix="${os}_${arch}.${ext}" + curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" | grep "\"browser_download_url\": \".*/download/.*/configlet.*${suffix}\"$" | cut -d'"' -f4 } main() { + local output_dir if [[ -d ./bin ]]; then output_dir="./bin" elif [[ $PWD == */bin ]]; then @@ -59,8 +46,26 @@ main() { return 1 fi - download_url="$(get_download_url)" - output_path="${output_dir}/latest-configlet.${ext}" + local os + case "$(uname)" in + Darwin*) os='macos' ;; + Linux*) os='linux' ;; + Windows*) os='windows' ;; + MINGW*) os='windows' ;; + MSYS_NT-*) os='windows' ;; + *) os='linux' ;; + esac + + local ext + case "${os}" in + windows*) ext='zip' ;; + *) ext='tar.gz' ;; + esac + + echo "Fetching configlet..." >&2 + local download_url + download_url="$(get_download_url "${os}" "${ext}")" + local output_path="${output_dir}/latest-configlet.${ext}" curl "${curlopts[@]}" --output "${output_path}" "${download_url}" case "${ext}" in @@ -69,6 +74,17 @@ main() { esac rm -f "${output_path}" + + local executable_ext + case "${os}" in + windows*) executable_ext='.exe' ;; + *) executable_ext='' ;; + esac + + local configlet_path="${output_dir}/configlet${executable_ext}" + local configlet_version + configlet_version="$(${configlet_path} --version)" + echo "Downloaded configlet ${configlet_version} to ${configlet_path}" } main From 37d75769d0e4102434a589cee1f6d7ccdf9fa0d5 Mon Sep 17 00:00:00 2001 From: 0xA <43623870+zylideum@users.noreply.github.com> Date: Wed, 9 Nov 2022 10:54:55 -0700 Subject: [PATCH 008/826] Fixed typo in Task 4 example --- exercises/concept/tisbury-treasure-hunt/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/tisbury-treasure-hunt/.docs/instructions.md b/exercises/concept/tisbury-treasure-hunt/.docs/instructions.md index ae5dbf8b52..b9e514e512 100644 --- a/exercises/concept/tisbury-treasure-hunt/.docs/instructions.md +++ b/exercises/concept/tisbury-treasure-hunt/.docs/instructions.md @@ -98,7 +98,7 @@ Re-format the coordinate as needed for accurate comparison. >>> create_record(('Brass Spyglass', '4B'), ('Abandoned Lighthouse', ('4', 'B'), 'Blue')) ('Brass Spyglass', '4B', 'Abandoned Lighthouse', ('4', 'B'), 'Blue') ->>> create_record(('Brass Spyglass', '4B'), (('1', 'C'), 'Seaside Cottages', 'blue')) +>>> create_record(('Brass Spyglass', '4B'), ('Seaside Cottages', ('1', 'C'), 'blue')) "not a match" ``` From 4374b84249c61e9a6fb13343306bca44f8c38fe0 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 1 Nov 2022 12:14:17 +0100 Subject: [PATCH 009/826] Added pdb --- docs/TRACEBACKS.md | 100 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index e4a407e1a6..3aecb00edf 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -188,8 +188,108 @@ Setting `PYTHONOPTIMIZE` to `1` is equivalent to running Python with the `-O` op Setting `PYTHONOPTIMIZE` to `2` is equivalent to running Python with the `-OO` option, which both disables assertions and removes docstrings from the bytcode. Reducing bytecode is one way to make the code run faster. +## Python Debugger + +Python has a built in debugger, [pdb][pdb]. It can be used to step through code and inspect variables. You can also set breakpoints with it. +To get started you have to first import pdb and then call pdb.set_trace() where you want to start debugging. + +```python +import pdb + +def add(num1, num2): + return num1 + num2 + +pdb.set_trace() +sum = add(1,5) +print(sum) +``` + +Running this code will give you a prompt where you can type in commands. Write `help` to get a list of commands. +The most common onces are `step` which steps into a function called at that line. `next` which steps over a function call and move to the next line. `where` which tells you which line you are on. Some other usefull commands are `whatis ` which tells you the type of a variable and `print()` which prints the value of a variable. You can also just use `` to print the value of a variable. Another command is `jump ` which jumps to a specific line number. + +Here are an example on how to use the debugger based on the code earlier: + +```python +>>> python pdb.py +... > c:\pdb.py(7)() +... -> sum = add(1,5) +... (Pdb) +>>> step +... > c:\pdb.py(3)add() +... -> def add(num1, num2): +... (Pdb) +>>> whatis num1 +... +>>> print(num2) +... 5 +>>> next +... > c:\pdb.py(4)add() +... -> return num1 + num2 +... (Pdb) +>>> jump 3 +... > c:\pdb.py(3)add() +... -> def add(num1, num2): +... (Pdb) +``` + +Breakpoints is setup by `break : ` where condition is an optional condition that has to be true for the breakpoint to be hit. You can simply write `break` to get a list of the breakpoints you have set. To disable a breakpoint you can write `disable `. To enable a breakpoint you can write `enable `. To delete a breakpoint you can write `clear `. To continue execution you can write `continue` or `c`. To exit the debugger you can write `quit` or `q`. + +Here are an example on how to use the debugger based on the code earlier: + +```python +>>> python pdb.py +... > c:\pdb.py(7)() +... -> sum = add(1,5) +... (Pdb) +>>> break +... +>>> break pdb:4 +... Breakpoint 1 at c:\pdb.py:4 +>>> break +... Num Type Disp Enb Where +... 2 breakpoint keep yes at c:\pdb.py:4 +>>> c # continue +... > c:\pdn.py(4)add() +... -> return num1 + num2 +>>> disable break 1 +... Disabled breakpoint 1 at c:\pdb.py:4 +>>> break +... Num Type Disp Enb Where +... 1 breakpoint keep no at c:\pdb.py:4 +... breakpoint already hit 1 time +>>> clear break 1 +... Deleted breakpoint 1 at c:\pdb.py:4 +>>> break +... +``` + +In python 3.7 and newer there is an easier way to create breakpoints You can simply write `breakpoint()` where you want to create a breakpoint. + +```python +import pdb + +def add(num1, num2): + breakpoint() + return num1 + num2 + +pdb.set_trace() +sum = add(1,5) +print(sum) +``` + +```python +>>> python pdb.py +... > c:\pdb.py(7)() +... -> sum = add(1,5) +... (Pdb) +>>> c # continue +... > c:\pdb.py(5)add() +... -> return num1 + num2 +``` + [assert]: https://realpython.com/python-assert-statement/ [AssertionError]: https://www.geeksforgeeks.org/python-assertion-error/ [floor divison operator]: https://www.codingem.com/python-floor-division [logging]: https://docs.python.org/3/howto/logging.html [print]: https://www.w3schools.com/python/ref_func_print.asp +[pdb]: https://www.geeksforgeeks.org/python-debugger-python-pdb/ From bac4d3d6ca45133e6936534d681c58f40d4f4fe3 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 1 Nov 2022 12:20:38 +0100 Subject: [PATCH 010/826] Update TRACEBACKS.md --- docs/TRACEBACKS.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 3aecb00edf..69deaa8310 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -190,7 +190,8 @@ Reducing bytecode is one way to make the code run faster. ## Python Debugger -Python has a built in debugger, [pdb][pdb]. It can be used to step through code and inspect variables. You can also set breakpoints with it. +Python has a built in debugger, [pdb][pdb]. +It can be used to step through code and inspect variables. You can also set breakpoints with it. To get started you have to first import pdb and then call pdb.set_trace() where you want to start debugging. ```python @@ -204,8 +205,13 @@ sum = add(1,5) print(sum) ``` -Running this code will give you a prompt where you can type in commands. Write `help` to get a list of commands. -The most common onces are `step` which steps into a function called at that line. `next` which steps over a function call and move to the next line. `where` which tells you which line you are on. Some other usefull commands are `whatis ` which tells you the type of a variable and `print()` which prints the value of a variable. You can also just use `` to print the value of a variable. Another command is `jump ` which jumps to a specific line number. +Running this code will give you a prompt where you can type in commands. +Write `help` to get a list of commands. +The most common onces are `step` which steps into a function called at that line. +`next` which steps over a function call and move to the next line. `where` which tells you which line you are on. +Some other usefull commands are `whatis ` which tells you the type of a variable and `print()` which prints the value of a variable. +You can also just use `` to print the value of a variable. +Another command is `jump ` which jumps to a specific line number. Here are an example on how to use the debugger based on the code earlier: @@ -232,7 +238,12 @@ Here are an example on how to use the debugger based on the code earlier: ... (Pdb) ``` -Breakpoints is setup by `break : ` where condition is an optional condition that has to be true for the breakpoint to be hit. You can simply write `break` to get a list of the breakpoints you have set. To disable a breakpoint you can write `disable `. To enable a breakpoint you can write `enable `. To delete a breakpoint you can write `clear `. To continue execution you can write `continue` or `c`. To exit the debugger you can write `quit` or `q`. +Breakpoints is setup by `break : ` where condition is an optional condition that has to be true for the breakpoint to be hit. +You can simply write `break` to get a list of the breakpoints you have set. +To disable a breakpoint you can write `disable `. +To enable a breakpoint you can write `enable `. +To delete a breakpoint you can write `clear `. +To continue execution you can write `continue` or `c`. To exit the debugger you can write `quit` or `q`. Here are an example on how to use the debugger based on the code earlier: @@ -288,7 +299,7 @@ print(sum) ``` [assert]: https://realpython.com/python-assert-statement/ -[AssertionError]: https://www.geeksforgeeks.org/python-assertion-error/ +[assertionerror]: https://www.geeksforgeeks.org/python-assertion-error/ [floor divison operator]: https://www.codingem.com/python-floor-division [logging]: https://docs.python.org/3/howto/logging.html [print]: https://www.w3schools.com/python/ref_func_print.asp From 623fe0e8b7fa3bb4ac1bc8b2ec2509e7525860a5 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 1 Nov 2022 12:27:06 +0100 Subject: [PATCH 011/826] Spelling fixes --- docs/TRACEBACKS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 69deaa8310..41ebf756ee 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -207,13 +207,13 @@ print(sum) Running this code will give you a prompt where you can type in commands. Write `help` to get a list of commands. -The most common onces are `step` which steps into a function called at that line. -`next` which steps over a function call and move to the next line. `where` which tells you which line you are on. -Some other usefull commands are `whatis ` which tells you the type of a variable and `print()` which prints the value of a variable. +The most common ones are `step` which steps into a function called at that line. +`next` steps over a function call and move to the next line. `where` tells you which line you are on. +Some other useful commands are `whatis ` which tells you the type of a variable and `print()` which prints the value of a variable. You can also just use `` to print the value of a variable. Another command is `jump ` which jumps to a specific line number. -Here are an example on how to use the debugger based on the code earlier: +Here is an example of how to use the debugger based on the code earlier: ```python >>> python pdb.py @@ -238,14 +238,14 @@ Here are an example on how to use the debugger based on the code earlier: ... (Pdb) ``` -Breakpoints is setup by `break : ` where condition is an optional condition that has to be true for the breakpoint to be hit. +Breakpoints are set up by `break : ` where the condition is an optional condition that has to be true for the breakpoint to be hit. You can simply write `break` to get a list of the breakpoints you have set. To disable a breakpoint you can write `disable `. To enable a breakpoint you can write `enable `. To delete a breakpoint you can write `clear `. To continue execution you can write `continue` or `c`. To exit the debugger you can write `quit` or `q`. -Here are an example on how to use the debugger based on the code earlier: +Here is an example of how to use the debugger based on the code earlier: ```python >>> python pdb.py From 996df0e92ab31d234ff6e444c4fc6171c653a98a Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 1 Nov 2022 13:29:24 +0100 Subject: [PATCH 012/826] Update TRACEBACKS.md --- docs/TRACEBACKS.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 41ebf756ee..8661efbce9 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -277,13 +277,11 @@ Here is an example of how to use the debugger based on the code earlier: In python 3.7 and newer there is an easier way to create breakpoints You can simply write `breakpoint()` where you want to create a breakpoint. ```python -import pdb - def add(num1, num2): breakpoint() return num1 + num2 -pdb.set_trace() +breakpoint() sum = add(1,5) print(sum) ``` From c490085cf9091b453305c05b240f42a3e491ccd6 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Fri, 18 Nov 2022 09:20:37 +0100 Subject: [PATCH 013/826] Apply suggestions from code review Co-authored-by: BethanyG --- docs/TRACEBACKS.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 8661efbce9..0a80c048a3 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -192,7 +192,7 @@ Reducing bytecode is one way to make the code run faster. Python has a built in debugger, [pdb][pdb]. It can be used to step through code and inspect variables. You can also set breakpoints with it. -To get started you have to first import pdb and then call pdb.set_trace() where you want to start debugging. +To get started you have to first `import pdb` and then call `pdb.set_trace()` where you want to start debugging: ```python import pdb @@ -205,7 +205,7 @@ sum = add(1,5) print(sum) ``` -Running this code will give you a prompt where you can type in commands. +Running this code will give you a pdb prompt where you can type in commands. Write `help` to get a list of commands. The most common ones are `step` which steps into a function called at that line. `next` steps over a function call and move to the next line. `where` tells you which line you are on. @@ -213,7 +213,8 @@ Some other useful commands are `whatis ` which tells you the type of a You can also just use `` to print the value of a variable. Another command is `jump ` which jumps to a specific line number. -Here is an example of how to use the debugger based on the code earlier: +Here is a small example of how to use the debugger based on the code earlier. +Note that for this and following examples, MacOS or Linux platforms would have file paths using forward slashes: ```python >>> python pdb.py @@ -245,7 +246,7 @@ To enable a breakpoint you can write `enable `. To delete a breakpoint you can write `clear `. To continue execution you can write `continue` or `c`. To exit the debugger you can write `quit` or `q`. -Here is an example of how to use the debugger based on the code earlier: +Here is an example of how to use the above debugger commands based on the code earlier: ```python >>> python pdb.py @@ -274,7 +275,8 @@ Here is an example of how to use the debugger based on the code earlier: ... ``` -In python 3.7 and newer there is an easier way to create breakpoints You can simply write `breakpoint()` where you want to create a breakpoint. +In Python 3.7+ there is an easier way to create breakpoints +Simply writing `breakpoint()` where needed will create one. ```python def add(num1, num2): From 979f7f7a6ea1ef2698e362a181c0f87cebf60210 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Fri, 18 Nov 2022 09:21:47 +0100 Subject: [PATCH 014/826] Update TRACEBACKS.md --- docs/TRACEBACKS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 0a80c048a3..acca29b041 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -246,7 +246,7 @@ To enable a breakpoint you can write `enable `. To delete a breakpoint you can write `clear `. To continue execution you can write `continue` or `c`. To exit the debugger you can write `quit` or `q`. -Here is an example of how to use the above debugger commands based on the code earlier: +Here is an example of how to use the above debugger commands based on the code earlier: ```python >>> python pdb.py @@ -275,7 +275,7 @@ Here is an example of how to use the above debugger commands based on the code ... ``` -In Python 3.7+ there is an easier way to create breakpoints +In Python 3.7+ there is an easier way to create breakpoints Simply writing `breakpoint()` where needed will create one. ```python From 5983e7e946bba0d939d83742aca7102e8b8207a2 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Fri, 18 Nov 2022 09:30:55 +0100 Subject: [PATCH 015/826] Update TRACEBACKS.md --- docs/TRACEBACKS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index acca29b041..ddda2eb8bd 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -191,7 +191,8 @@ Reducing bytecode is one way to make the code run faster. ## Python Debugger Python has a built in debugger, [pdb][pdb]. -It can be used to step through code and inspect variables. You can also set breakpoints with it. +It can be used to step through code and inspect variables. +You can also set breakpoints with it. To get started you have to first `import pdb` and then call `pdb.set_trace()` where you want to start debugging: ```python From 7a1e726a2b3ee6c75ab2f5638dbc5064932939e4 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:38:29 -0600 Subject: [PATCH 016/826] Create config.json --- .../practice/leap/.approaches/config.json | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 exercises/practice/leap/.approaches/config.json diff --git a/exercises/practice/leap/.approaches/config.json b/exercises/practice/leap/.approaches/config.json new file mode 100644 index 0000000000..3b5d57f997 --- /dev/null +++ b/exercises/practice/leap/.approaches/config.json @@ -0,0 +1,29 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "5d42dc83-2473-425a-90bd-bf03f92b8c8b", + "slug": "boolean-chain", + "title": "Boolean chain", + "blurb": "Use a chain of boolean expressions.", + "authors": ["bobahop"] + }, + { + "uuid": "9952fef5-9f2f-4575-94fc-bc4e96593cd6", + "slug": "ternary-operator", + "title": "Ternary operator", + "blurb": "Use a ternary operator of boolean expressions.", + "authors": ["bobahop"] + }, + { + "uuid": "66302791-0770-4f08-beaa-251c49e280a2", + "slug": "datetime-addition", + "title": "datetime addition", + "blurb": "Use datetime addition.", + "authors": ["bobahop"] + } + ] +} From ab6b85aea0957b1f690c879d661a102c8ce343f9 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:58:09 -0600 Subject: [PATCH 017/826] Create config.json --- exercises/practice/leap/.articles/config.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 exercises/practice/leap/.articles/config.json diff --git a/exercises/practice/leap/.articles/config.json b/exercises/practice/leap/.articles/config.json new file mode 100644 index 0000000000..acafe3b22d --- /dev/null +++ b/exercises/practice/leap/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "e54a0a87-cb9d-4d5c-aa86-93a239ffdd8c", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach for determining a leap year.", + "authors": ["bobahop"] + } + ] +} From 086bf51fb0e0d0714878be47dc5867dcca9b2b08 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:59:13 -0600 Subject: [PATCH 018/826] Create snippet.md --- exercises/practice/leap/.articles/performance/snippet.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 exercises/practice/leap/.articles/performance/snippet.md diff --git a/exercises/practice/leap/.articles/performance/snippet.md b/exercises/practice/leap/.articles/performance/snippet.md new file mode 100644 index 0000000000..2b3e77bb99 --- /dev/null +++ b/exercises/practice/leap/.articles/performance/snippet.md @@ -0,0 +1,5 @@ +``` +if statements 2019: 8.861289999913425e-08 +ternary 2019: 1.0278620000462979e-07 +datetime add 2019: 6.689728000201284e-07 +``` From 2ab585fe5fc2d33578290a6441dd590fdff72fa4 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:02:29 -0600 Subject: [PATCH 019/826] Create Benchmark.py --- .../.articles/performance/code/Benchmark.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 exercises/practice/leap/.articles/performance/code/Benchmark.py diff --git a/exercises/practice/leap/.articles/performance/code/Benchmark.py b/exercises/practice/leap/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..467a595292 --- /dev/null +++ b/exercises/practice/leap/.articles/performance/code/Benchmark.py @@ -0,0 +1,88 @@ +import timeit + +loops = 1_000_000 + +val = timeit.timeit("""leap_year(1900)""", + """ +def leap_year(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + +""", number=loops) / loops + +print(f"if statements 1900: {val}") + +val = timeit.timeit("""leap_year(2000)""", + """ +def leap_year(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + +""", number=loops) / loops + +print(f"if statements 2000: {val}") + + +val = timeit.timeit("""leap_year(2019)""", + """ +def leap_year(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + +""", number=loops) / loops + +print(f"if statements 2019: {val}") + +val = timeit.timeit("""leap_year(2020)""", + """ +def leap_year(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + +""", number=loops) / loops + +print(f"if statements 2020: {val}") + +val = timeit.timeit("""leap_year(1900)""", + """ +def leap_year(year): + return not year % 400 if not year % 100 else not year % 4 + +""", number=loops) / loops + +print(f"ternary 1900: {val}") + +val = timeit.timeit("""leap_year(2000)""", + """ +def leap_year(year): + return not year % 400 if not year % 100 else not year % 4 + +""", number=loops) / loops + +print(f"ternary 2000: {val}") + +val = timeit.timeit("""leap_year(2019)""", + """ +def leap_year(year): + return not year % 400 if not year % 100 else not year % 4 + +""", number=loops) / loops + +print(f"ternary 2019: {val}") + +val = timeit.timeit("""leap_year(2020)""", + """ +def leap_year(year): + return not year % 400 if not year % 100 else not year % 4 + +""", number=loops) / loops + +print(f"ternary 2020: {val}") + +val = timeit.timeit("""leap_year(2019)""", + """ +import datetime + +def leap_year(year): + return (datetime.datetime(year, 2, 28) + + datetime.timedelta(days=1)).day == 29 + +""", number=loops) / loops + +print(f"datetime add 2019: {val}") From f067a6d8478c7eb75a2a62af9e908a60deb12489 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:10:55 -0600 Subject: [PATCH 020/826] Create content.md --- .../leap/.articles/performance/content.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 exercises/practice/leap/.articles/performance/content.md diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md new file mode 100644 index 0000000000..f3a539fcb6 --- /dev/null +++ b/exercises/practice/leap/.articles/performance/content.md @@ -0,0 +1,36 @@ +# Performance + +In this approach, we'll find out how to most efficiently calculate if a year is a leap year in Python. + +The [approaches page][approaches] lists two idiomatic approaches to this exercise: + +1. [Using the boolean chain][approach-boolean-chain] +2. [Using the ternary operator][approach-ternary-operator] + +For our performance investigation, we'll also include a third approach that [uses datetime addition][approach-datetime-addition]. + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. + +``` +if statements 1900: 1.468243999988772e-07 +if statements 2000: 1.3710349999018945e-07 +if statements 2019: 8.861289999913425e-08 +if statements 2020: 1.21072500012815e-07 +ternary 1900: 1.091794999956619e-07 +ternary 2000: 1.0275900000124239e-07 +ternary 2019: 1.0278620000462979e-07 +ternary 2020: 1.0290379999787546e-07 +datetime add 2019: 6.689728000201284e-07 +``` + +You can see that the ternary operator was faster than the chain of conditions. +Adding the `datetime` may not only be a "cheat", but it was slower than the ternary operator. + +[approaches]: https://exercism.org/tracks/python/exercises/leap/approaches +[approach-boolean-chain]: https://exercism.org/tracks/python/exercises/leap/approaches/boolean-chain +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/leap/approaches/ternary-operator +[approach-datetime-addition]: https://exercism.org/tracks/python/exercises/leap/approaches/datetime-addition +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/leap/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html From 019adb5fbfaec30019e1e41dc24a6e2acf4fc871 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:25:56 -0600 Subject: [PATCH 021/826] Create introduction.md --- .../practice/leap/.approaches/introduction.md | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 exercises/practice/leap/.approaches/introduction.md diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md new file mode 100644 index 0000000000..2ce1069844 --- /dev/null +++ b/exercises/practice/leap/.approaches/introduction.md @@ -0,0 +1,67 @@ +# Introduction + +There are various idiomatic approaches to solve Leap. +You can use a chain of boolean expressions to test the conditions. +Or you can use a [ternary operator][ternary-operator]. + +## General guidance + +The key to solving Leap is to know if the year is evenly divisible by `4`, `100` and `400`. +For determining that, you will use the [modulo operator][modulo-operator]. + +## Approach: Chain of Boolean expressions + +```python +def leap_year(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + +``` + +For more information, check the [Boolean chain approach][approach-boolean-chain]. + +## Approach: Ternary operator of Boolean expressions + +```python +def leap_year(year): + return (not year % 400 if not year % 100 else not year % 4) + +``` + +For more information, check the [Ternary operator approach][approach-ternary-operator]. + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +### Approach: datetime addition + +Add a day to February 28th for the year and see if the new day is the 29th. For more information, see the [`datetime` addition approach][approach-datetime-addition]. + +stiick code here for now + +```python +import datetime + + +def leap_year(year): + return (datetime.datetime(year, 2, 28) + + datetime.timedelta(days=1)).day == 29 + +``` + +## Which approach to use? + +- The chain of boolean expressions shhould be the most efficient, as it proceeds from the most likely to least likely conditions. +It has a maximum of three checks. +- The ternary operator has a maximum of only two checks, but it starts from a less likely condition. +Yet the ternary operator was faster in benchmarking. +- Using `datetime` addition may be considered a "cheat" for the exercise, and it was slower than the ternary operator in benchmarking. + +For more information, check the [Performance article][article-performance]. + +[modulo-operator]: https://realpython.com/python-modulo-operator/ +[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ +[approach-boolean-chain]: https://exercism.org/tracks/python/exercises/leap/approaches/boolean-chain +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/leap/approaches/ternary-operator +[approach-datetime-addition]: https://exercism.org/tracks/python/exercises/leap/approaches/datetime-addition +[article-performance]: https://exercism.org/tracks/python/exercises/leap/articles/performance From a785caa11affdf505de8e2772ba59f211c83f119 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:26:57 -0600 Subject: [PATCH 022/826] Create snippet.txt --- exercises/practice/leap/.approaches/boolean-chain/snippet.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 exercises/practice/leap/.approaches/boolean-chain/snippet.txt diff --git a/exercises/practice/leap/.approaches/boolean-chain/snippet.txt b/exercises/practice/leap/.approaches/boolean-chain/snippet.txt new file mode 100644 index 0000000000..df5c3e62de --- /dev/null +++ b/exercises/practice/leap/.approaches/boolean-chain/snippet.txt @@ -0,0 +1,2 @@ +def leap_year(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) From 7383c4eec4390f3344e853721934f2eae3804297 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:27:40 -0600 Subject: [PATCH 023/826] Create snippet.txt --- .../practice/leap/.approaches/ternary-operator/snippet.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 exercises/practice/leap/.approaches/ternary-operator/snippet.txt diff --git a/exercises/practice/leap/.approaches/ternary-operator/snippet.txt b/exercises/practice/leap/.approaches/ternary-operator/snippet.txt new file mode 100644 index 0000000000..bc7d90c3f3 --- /dev/null +++ b/exercises/practice/leap/.approaches/ternary-operator/snippet.txt @@ -0,0 +1,2 @@ +def leap_year(year): + return (not year % 400 if not year % 100 else not year % 4) From b51761126b310408bfd6cf23b36bc84b40b5f19f Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:28:39 -0600 Subject: [PATCH 024/826] Create snippet.txt --- .../practice/leap/.approaches/datetime-addition/snippet.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 exercises/practice/leap/.approaches/datetime-addition/snippet.txt diff --git a/exercises/practice/leap/.approaches/datetime-addition/snippet.txt b/exercises/practice/leap/.approaches/datetime-addition/snippet.txt new file mode 100644 index 0000000000..b02df891b0 --- /dev/null +++ b/exercises/practice/leap/.approaches/datetime-addition/snippet.txt @@ -0,0 +1,6 @@ +import datetime + + +def leap_year(year): + return (datetime.datetime(year, 2, 28) + + datetime.timedelta(days=1)).day == 29 From 6532f014a716986554e811a77ed281d4baa10dfd Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:46:06 -0600 Subject: [PATCH 025/826] Create content.md --- .../leap/.approaches/boolean-chain/content.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 exercises/practice/leap/.approaches/boolean-chain/content.md diff --git a/exercises/practice/leap/.approaches/boolean-chain/content.md b/exercises/practice/leap/.approaches/boolean-chain/content.md new file mode 100644 index 0000000000..c63b0c9b17 --- /dev/null +++ b/exercises/practice/leap/.approaches/boolean-chain/content.md @@ -0,0 +1,44 @@ +# Chain of boolean expressions + +```python +def leap_year(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + +``` + +The first boolean expression uses the [modulo operator][modulo-operator] to check if the year is evenly divided by `4`. +- If the year is not evenly divisible by `4`, then the chain will "short circuit" due to the next operator being a [logical AND][logical-and] and`), and will return `False`. +- If the year _is_ evenly divisible by `4`, then the year is checked to _not_ be evenly divisible by `100`. +- If the year is not evenly divisible by `100`, then the expression is `True` and the chain will "short-circuit" to return `True`, +since the next operator is a [logical OR][logical-or] (`or`). +- If the year _is_ evenly divisible by `100`, then the expression is `False`, and the returned value from the chain will be if the year is evenly divisible by `400`. + +| year | year % 4 == 0 | year % 100 != 0 | year % 400 == 0 | is leap year | +| ---- | ------------- | --------------- | --------------- | ------------ | +| 2020 | True | True | not evaluated | True | +| 2019 | False | not evaluated | not evaluated | False | +| 2000 | True | False | True | True | +| 1900 | True | False | False | False | + + +The chain of boolean expressions is efficient, as it proceeds from testing the most likely to least likely conditions. +It is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. + +## Shortening + +By using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. +For example + +```python +def leap_year(year): + return not year % 4 and (year % 100 != 0 or not year % 400) + +``` + +It can be thought of as the expression _not_ having a remainder. + +[modulo-operator]: https://realpython.com/python-modulo-operator/ +[logical-and]: https://realpython.com/python-and-operator/ +[logical-or]: https://realpython.com/python-or-operator/ +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[not-operator]: https://realpython.com/python-not-operator/ From 0ea174b2497df342f140daf6ed495335966be903 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:49:42 -0600 Subject: [PATCH 026/826] Update introduction.md --- exercises/practice/leap/.approaches/introduction.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md index 2ce1069844..a0a1a18169 100644 --- a/exercises/practice/leap/.approaches/introduction.md +++ b/exercises/practice/leap/.approaches/introduction.md @@ -51,10 +51,11 @@ def leap_year(year): ## Which approach to use? -- The chain of boolean expressions shhould be the most efficient, as it proceeds from the most likely to least likely conditions. +- The chain of boolean expressions should be the most efficient, as it proceeds from the most likely to least likely conditions. It has a maximum of three checks. +It is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. - The ternary operator has a maximum of only two checks, but it starts from a less likely condition. -Yet the ternary operator was faster in benchmarking. +The ternary operator was faster in benchmarking when the year was a leap year or was evenly divisible by `100`. - Using `datetime` addition may be considered a "cheat" for the exercise, and it was slower than the ternary operator in benchmarking. For more information, check the [Performance article][article-performance]. From 0cdaf5328d39c6ef988408a9b997b5af9c367bc7 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 07:52:00 -0600 Subject: [PATCH 027/826] Update content.md --- exercises/practice/leap/.articles/performance/content.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index f3a539fcb6..387de7902b 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -25,8 +25,9 @@ ternary 2020: 1.0290379999787546e-07 datetime add 2019: 6.689728000201284e-07 ``` -You can see that the ternary operator was faster than the chain of conditions. -Adding the `datetime` may not only be a "cheat", but it was slower than the ternary operator. +The boolean chain is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. +The ternary operator is faster in benchmarking when the year is a leap year or is evenly divisible by `100`. +Adding the `datetime` may not only be a "cheat", but it is slower than the ternary operator. [approaches]: https://exercism.org/tracks/python/exercises/leap/approaches [approach-boolean-chain]: https://exercism.org/tracks/python/exercises/leap/approaches/boolean-chain From 01f0fdf5f3ec1df697e072b64783eef0482800a9 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 09:54:13 -0600 Subject: [PATCH 028/826] Create content.md --- .../.approaches/ternary-operator/content.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 exercises/practice/leap/.approaches/ternary-operator/content.md diff --git a/exercises/practice/leap/.approaches/ternary-operator/content.md b/exercises/practice/leap/.approaches/ternary-operator/content.md new file mode 100644 index 0000000000..b4a81f5a6a --- /dev/null +++ b/exercises/practice/leap/.approaches/ternary-operator/content.md @@ -0,0 +1,33 @@ +# Ternary operator + +```python +def leap_year(year): + return (not year % 400 if not year % 100 else not year % 4) + +``` + +A [ternary operator][ternary-operator] uses a maximum of two checks to determine if a year is a leap year. + +It starts by testing the outlier condition of the year being evenly divisible by `100`. +It does this by using the [modulo operator][modulo-operator]. +And by using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. + +- If the year is evenly divisible by `100`, then the expression is `True`, and the ternary operator returns if the year is evenly divisible by `400`. +- If the year is _not_ evenly divisible by `100`, then the expression is `False`, and the ternary operator returns if the year is evenly divisible by `4`. + +| year | year % 100 == 0 | year % 400 == 0 | year % 4 == 0 | is leap year | +| ---- | --------------- | --------------- | -------------- | ------------ | +| 2020 | False | not evaluated | True | True | +| 2019 | False | not evaluated | False | False | +| 2000 | True | True | not evaluated | True | +| 1900 | True | False | not evaluated | False | + +Although it uses a maximum of only two checks, the ternary operator tests an outlier condition first, +making it less efficient than another approach that would first test if the year is evenly divisible by `4`, +which is more likely than the year being evenly divisible by `100`. +The ternary operator was fastest in benchmarking when the year was a leap year or was evenly divisible by 100. + +[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ +[modulo-operator]: https://realpython.com/python-modulo-operator/ +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[not-operator]: https://realpython.com/python-not-operator/ From 1976c063e2b5de4a2eb714b044c3d7c026118771 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:09:39 -0600 Subject: [PATCH 029/826] Create content.md --- .../.approaches/datetime-addition/content.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 exercises/practice/leap/.approaches/datetime-addition/content.md diff --git a/exercises/practice/leap/.approaches/datetime-addition/content.md b/exercises/practice/leap/.approaches/datetime-addition/content.md new file mode 100644 index 0000000000..862abcfe1b --- /dev/null +++ b/exercises/practice/leap/.approaches/datetime-addition/content.md @@ -0,0 +1,26 @@ +# `datetime` addition + +```python +import datetime + + +def leap_year(year): + return (datetime.datetime(year, 2, 28) + + datetime.timedelta(days=1)).day == 29 + +``` + +```exercism/caution +This approach may be considered a "cheat" for this exercise. +``` + +By adding a day to February 28th for the `year`, you can see if the new day is the 29th or the 1st. +If it is the 29th, then the function returns `True` for the `year` being a leap year. + +- A new [datetime][datetime] object is created for Febuary 28th of the `year`. +- Then the [timedelta][timedelta] of one day is added to that `datetime`, +and the function returns if the [day][day] property of the resulting `datetime` object is the 29th. + +[timedelta]: https://docs.python.org/3/library/datetime.html#timedelta-objects +[day]: https://docs.python.org/3/library/datetime.html#datetime.datetime.day +[datetime]: https://docs.python.org/3/library/datetime.html#datetime-objects From de0e83123a3e3fcdda196f3782aadc15a6279bd4 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:12:51 -0600 Subject: [PATCH 030/826] Update introduction.md --- .../practice/leap/.approaches/introduction.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md index a0a1a18169..a5c8c829a5 100644 --- a/exercises/practice/leap/.approaches/introduction.md +++ b/exercises/practice/leap/.approaches/introduction.md @@ -37,26 +37,16 @@ Besides the aforementioned, idiomatic approaches, you could also approach the ex Add a day to February 28th for the year and see if the new day is the 29th. For more information, see the [`datetime` addition approach][approach-datetime-addition]. -stiick code here for now - -```python -import datetime - - -def leap_year(year): - return (datetime.datetime(year, 2, 28) - + datetime.timedelta(days=1)).day == 29 - -``` - ## Which approach to use? - The chain of boolean expressions should be the most efficient, as it proceeds from the most likely to least likely conditions. It has a maximum of three checks. It is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. +Since most years fit those conditions, it is overall the most efficient approach. - The ternary operator has a maximum of only two checks, but it starts from a less likely condition. -The ternary operator was faster in benchmarking when the year was a leap year or was evenly divisible by `100`. -- Using `datetime` addition may be considered a "cheat" for the exercise, and it was slower than the ternary operator in benchmarking. +The ternary operator was faster in benchmarking when the year was a leap year or was evenly divisible by `100`, +but those are the least likely conditions. +- Using `datetime` addition may be considered a "cheat" for the exercise, and it was slower than the other approaches in benchmarking. For more information, check the [Performance article][article-performance]. From 207b8f7298088df6bd06850c488c86a458890524 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:15:14 -0600 Subject: [PATCH 031/826] Update content.md --- exercises/practice/leap/.articles/performance/content.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index 387de7902b..ce5ead1ca2 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -25,9 +25,11 @@ ternary 2020: 1.0290379999787546e-07 datetime add 2019: 6.689728000201284e-07 ``` -The boolean chain is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. -The ternary operator is faster in benchmarking when the year is a leap year or is evenly divisible by `100`. -Adding the `datetime` may not only be a "cheat", but it is slower than the ternary operator. +- The boolean chain is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. +Since most years fit those conditions, it is overall the most efficient approach. +- The ternary operator is faster in benchmarking when the year is a leap year or is evenly divisible by `100`, +but those are the least likely conditions. +- Adding to the `datetime` may not only be a "cheat", but it is slower than the other approaches. [approaches]: https://exercism.org/tracks/python/exercises/leap/approaches [approach-boolean-chain]: https://exercism.org/tracks/python/exercises/leap/approaches/boolean-chain From a87e359bc946dc368d9ca0e837683342cc114536 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:19:09 -0600 Subject: [PATCH 032/826] Update content.md --- exercises/practice/leap/.approaches/boolean-chain/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/leap/.approaches/boolean-chain/content.md b/exercises/practice/leap/.approaches/boolean-chain/content.md index c63b0c9b17..4c0e24456c 100644 --- a/exercises/practice/leap/.approaches/boolean-chain/content.md +++ b/exercises/practice/leap/.approaches/boolean-chain/content.md @@ -7,7 +7,7 @@ def leap_year(year): ``` The first boolean expression uses the [modulo operator][modulo-operator] to check if the year is evenly divided by `4`. -- If the year is not evenly divisible by `4`, then the chain will "short circuit" due to the next operator being a [logical AND][logical-and] and`), and will return `False`. +- If the year is not evenly divisible by `4`, then the chain will "short circuit" due to the next operator being a [logical AND][logical-and] {`and`), and will return `False`. - If the year _is_ evenly divisible by `4`, then the year is checked to _not_ be evenly divisible by `100`. - If the year is not evenly divisible by `100`, then the expression is `True` and the chain will "short-circuit" to return `True`, since the next operator is a [logical OR][logical-or] (`or`). From 543c6cbd5a7a9c914d881ca3cca13c5cc08cb1e2 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:21:16 -0600 Subject: [PATCH 033/826] Update content.md --- .../practice/leap/.approaches/ternary-operator/content.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exercises/practice/leap/.approaches/ternary-operator/content.md b/exercises/practice/leap/.approaches/ternary-operator/content.md index b4a81f5a6a..f6f8af3018 100644 --- a/exercises/practice/leap/.approaches/ternary-operator/content.md +++ b/exercises/practice/leap/.approaches/ternary-operator/content.md @@ -10,7 +10,7 @@ A [ternary operator][ternary-operator] uses a maximum of two checks to determine It starts by testing the outlier condition of the year being evenly divisible by `100`. It does this by using the [modulo operator][modulo-operator]. -And by using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. +Also, by using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. - If the year is evenly divisible by `100`, then the expression is `True`, and the ternary operator returns if the year is evenly divisible by `400`. - If the year is _not_ evenly divisible by `100`, then the expression is `False`, and the ternary operator returns if the year is evenly divisible by `4`. @@ -25,7 +25,8 @@ And by using the [falsiness][falsiness] of `0`, the [`not` operator][not-operato Although it uses a maximum of only two checks, the ternary operator tests an outlier condition first, making it less efficient than another approach that would first test if the year is evenly divisible by `4`, which is more likely than the year being evenly divisible by `100`. -The ternary operator was fastest in benchmarking when the year was a leap year or was evenly divisible by 100. +The ternary operator was fastest in benchmarking when the year was a leap year or was evenly divisible by `100`, +but those are the least likely conditions. [ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ [modulo-operator]: https://realpython.com/python-modulo-operator/ From 8b8d2c67de28c450642c09d9ecca09ca840447f6 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 15 Nov 2022 10:22:32 -0600 Subject: [PATCH 034/826] Update content.md --- .../practice/leap/.approaches/datetime-addition/content.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/leap/.approaches/datetime-addition/content.md b/exercises/practice/leap/.approaches/datetime-addition/content.md index 862abcfe1b..3748fa47ca 100644 --- a/exercises/practice/leap/.approaches/datetime-addition/content.md +++ b/exercises/practice/leap/.approaches/datetime-addition/content.md @@ -14,10 +14,10 @@ def leap_year(year): This approach may be considered a "cheat" for this exercise. ``` -By adding a day to February 28th for the `year`, you can see if the new day is the 29th or the 1st. -If it is the 29th, then the function returns `True` for the `year` being a leap year. +By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. +If it is the 29th, then the function returns `True` for the year being a leap year. -- A new [datetime][datetime] object is created for Febuary 28th of the `year`. +- A new [datetime][datetime] object is created for Febuary 28th of the year. - Then the [timedelta][timedelta] of one day is added to that `datetime`, and the function returns if the [day][day] property of the resulting `datetime` object is the 29th. From cbfcfaa4ab5dd55ba8a0972bece29e56b8452693 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:59:05 -0600 Subject: [PATCH 035/826] Update content.md --- exercises/practice/leap/.approaches/boolean-chain/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/leap/.approaches/boolean-chain/content.md b/exercises/practice/leap/.approaches/boolean-chain/content.md index 4c0e24456c..c909863fca 100644 --- a/exercises/practice/leap/.approaches/boolean-chain/content.md +++ b/exercises/practice/leap/.approaches/boolean-chain/content.md @@ -24,7 +24,7 @@ since the next operator is a [logical OR][logical-or] (`or`). The chain of boolean expressions is efficient, as it proceeds from testing the most likely to least likely conditions. It is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. -## Shortening +## Refactoring By using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. For example From 16abd717721ee0a632385fa7666d4d32405610ef Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 18 Nov 2022 03:53:21 -0600 Subject: [PATCH 036/826] Update content.md --- exercises/practice/leap/.approaches/ternary-operator/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/leap/.approaches/ternary-operator/content.md b/exercises/practice/leap/.approaches/ternary-operator/content.md index f6f8af3018..e20142ddc6 100644 --- a/exercises/practice/leap/.approaches/ternary-operator/content.md +++ b/exercises/practice/leap/.approaches/ternary-operator/content.md @@ -2,7 +2,7 @@ ```python def leap_year(year): - return (not year % 400 if not year % 100 else not year % 4) + return not year % 400 if not year % 100 else not year % 4 ``` From 4828855f8d21c0a1fc9b85e8e24569fbe0493b69 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 18 Nov 2022 03:53:38 -0600 Subject: [PATCH 037/826] Update snippet.txt --- .../practice/leap/.approaches/ternary-operator/snippet.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/leap/.approaches/ternary-operator/snippet.txt b/exercises/practice/leap/.approaches/ternary-operator/snippet.txt index bc7d90c3f3..037ba3d502 100644 --- a/exercises/practice/leap/.approaches/ternary-operator/snippet.txt +++ b/exercises/practice/leap/.approaches/ternary-operator/snippet.txt @@ -1,2 +1,2 @@ def leap_year(year): - return (not year % 400 if not year % 100 else not year % 4) + return not year % 400 if not year % 100 else not year % 4 From 5b2722ebf20fcf32d84a112e111e3aee3222efa6 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sat, 19 Nov 2022 11:13:59 +0100 Subject: [PATCH 038/826] Sync docs with problem-specifications Most of the docs changes recently have been formatting-related, though in a few exercises there have also been improvements to the descriptions in an attempt to make the exercises easier to understand. --- .../affine-cipher/.docs/instructions.md | 6 +- .../all-your-base/.docs/instructions.md | 17 ++-- .../practice/allergies/.docs/instructions.md | 31 ++++--- .../alphametics/.docs/instructions.md | 11 ++- .../armstrong-numbers/.docs/instructions.md | 4 +- .../atbash-cipher/.docs/instructions.md | 15 ++-- .../bank-account/.docs/instructions.md | 16 ++-- .../practice/beer-song/.docs/instructions.md | 14 ---- .../binary-search-tree/.docs/instructions.md | 39 ++++----- .../binary-search/.docs/instructions.md | 35 +++----- exercises/practice/bob/.docs/instructions.md | 6 +- .../practice/book-store/.docs/instructions.md | 15 +--- .../practice/bowling/.docs/instructions.md | 51 ++++++------ .../practice/change/.docs/instructions.md | 9 +-- .../circular-buffer/.docs/instructions.md | 37 ++++----- .../collatz-conjecture/.docs/instructions.md | 12 +-- .../practice/connect/.docs/instructions.md | 26 +++--- .../crypto-square/.docs/instructions.md | 31 +++---- .../practice/custom-set/.docs/instructions.md | 7 +- .../practice/darts/.docs/instructions.md | 22 +++-- .../practice/diamond/.docs/instructions.md | 27 +++---- .../.docs/instructions.md | 9 +-- .../diffie-hellman/.docs/instructions.md | 19 +++-- .../dnd-character/.docs/instructions.md | 36 ++++----- .../practice/dominoes/.docs/instructions.md | 8 +- .../practice/dot-dsl/.docs/instructions.md | 29 +++---- exercises/practice/etl/.docs/instructions.md | 23 ++---- .../flatten-array/.docs/instructions.md | 2 +- .../practice/food-chain/.docs/instructions.md | 8 +- .../practice/forth/.docs/instructions.md | 21 +++-- .../practice/gigasecond/.docs/instructions.md | 3 +- .../go-counting/.docs/instructions.md | 29 +++---- .../grade-school/.docs/instructions.md | 36 ++------- .../practice/grains/.docs/instructions.md | 23 ++---- exercises/practice/grep/.docs/instructions.md | 80 ++++--------------- .../practice/hamming/.docs/instructions.md | 16 ++-- .../practice/hangman/.docs/instructions.md | 16 ++-- .../hello-world/.docs/instructions.md | 9 ++- .../practice/house/.docs/instructions.md | 13 ++- .../isbn-verifier/.docs/instructions.md | 20 ++--- .../kindergarten-garden/.docs/instructions.md | 18 ++--- .../practice/knapsack/.docs/instructions.md | 29 +++---- .../.docs/instructions.md | 10 +-- exercises/practice/leap/.docs/instructions.md | 12 ++- .../practice/ledger/.docs/instructions.md | 17 ++-- .../linked-list/.docs/instructions.md | 46 +++++------ .../practice/list-ops/.docs/instructions.md | 25 +++--- exercises/practice/luhn/.docs/instructions.md | 24 +++--- .../practice/markdown/.docs/instructions.md | 20 +++-- .../matching-brackets/.docs/instructions.md | 4 +- .../practice/matrix/.docs/instructions.md | 9 +-- .../practice/meetup/.docs/instructions.md | 54 ++++++++++--- .../minesweeper/.docs/instructions.md | 15 ++-- .../practice/nth-prime/.docs/instructions.md | 6 +- .../ocr-numbers/.docs/instructions.md | 8 +- .../practice/paasio/.docs/instructions.md | 9 +-- .../palindrome-products/.docs/instructions.md | 21 ++--- .../practice/pangram/.docs/instructions.md | 8 +- .../perfect-numbers/.docs/instructions.md | 10 ++- .../phone-number/.docs/instructions.md | 6 +- .../practice/pig-latin/.docs/instructions.md | 19 ++--- .../practice/poker/.docs/instructions.md | 5 +- exercises/practice/pov/.docs/instructions.md | 25 +++--- .../prime-factors/.docs/instructions.md | 26 +++--- .../protein-translation/.docs/instructions.md | 7 +- .../pythagorean-triplet/.docs/instructions.md | 5 +- .../rail-fence-cipher/.docs/instructions.md | 10 +-- .../practice/raindrops/.docs/instructions.md | 6 +- .../rational-numbers/.docs/instructions.md | 9 ++- .../practice/react/.docs/instructions.md | 15 ++-- .../practice/rectangles/.docs/instructions.md | 3 +- .../resistor-color-duo/.docs/instructions.md | 24 +++--- .../resistor-color/.docs/instructions.md | 7 +- .../practice/rest-api/.docs/instructions.md | 14 +++- .../rna-transcription/.docs/instructions.md | 17 ++-- .../practice/robot-name/.docs/instructions.md | 12 ++- .../robot-simulator/.docs/instructions.md | 11 +-- .../roman-numerals/.docs/instructions.md | 30 ++++--- .../rotational-cipher/.docs/instructions.md | 8 +- .../run-length-encoding/.docs/instructions.md | 12 +-- .../saddle-points/.docs/instructions.md | 10 +-- .../practice/satellite/.docs/instructions.md | 14 ++-- exercises/practice/say/.docs/instructions.md | 17 ++-- .../scale-generator/.docs/instructions.md | 57 +++++-------- .../secret-handshake/.docs/instructions.md | 7 +- .../practice/series/.docs/instructions.md | 10 +-- .../sgf-parsing/.docs/instructions.md | 29 +++++-- .../practice/sieve/.docs/instructions.md | 22 +++-- .../simple-cipher/.docs/instructions.md | 69 +++++++--------- .../simple-linked-list/.docs/instructions.md | 21 ++--- .../practice/space-age/.docs/instructions.md | 5 +- .../spiral-matrix/.docs/instructions.md | 4 +- .../sum-of-multiples/.docs/instructions.md | 6 +- .../practice/tournament/.docs/instructions.md | 13 +-- .../practice/transpose/.docs/instructions.md | 8 +- .../tree-building/.docs/instructions.md | 17 ++-- .../practice/triangle/.docs/instructions.md | 16 ++-- .../twelve-days/.docs/instructions.md | 3 +- .../practice/two-bucket/.docs/instructions.md | 15 +++- .../practice/two-fer/.docs/instructions.md | 3 +- .../.docs/instructions.md | 6 +- .../practice/word-count/.docs/instructions.md | 2 +- .../word-search/.docs/instructions.md | 9 +-- .../practice/wordy/.docs/instructions.md | 17 +--- .../practice/yacht/.docs/instructions.md | 23 +++--- .../zebra-puzzle/.docs/instructions.md | 4 +- .../practice/zipper/.docs/instructions.md | 11 ++- 107 files changed, 827 insertions(+), 1038 deletions(-) diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 6855736fa7..2ad6d15215 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -6,7 +6,7 @@ The affine cipher is a type of monoalphabetic substitution cipher. Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value. Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the atbash cipher, because it has many more keys. -[comment]: # ( monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic ) +[//]: # ( monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic ) ## Encryption @@ -52,7 +52,7 @@ The MMI of `a` is `x` such that the remainder after dividing `ax` by `m` is `1`: ax mod m = 1 ``` -More information regarding how to find a Modular Multiplicative Inverse and what it means can be found in the [related Wikipedia article][MMI]. +More information regarding how to find a Modular Multiplicative Inverse and what it means can be found in the [related Wikipedia article][mmi]. ## General Examples @@ -70,5 +70,5 @@ Finding MMI for `a = 15`: - `(15 * 7) mod 26 = 1`, ie. `105 mod 26 = 1` - `7` is the MMI of `15 mod 26` -[MMI]: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse +[mmi]: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse [coprime-integers]: https://en.wikipedia.org/wiki/Coprime_integers diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md index 2de87cffd7..d5a2cde652 100644 --- a/exercises/practice/all-your-base/.docs/instructions.md +++ b/exercises/practice/all-your-base/.docs/instructions.md @@ -2,31 +2,32 @@ Convert a number, represented as a sequence of digits in one base, to any other base. -Implement general base conversion. Given a number in base **a**, -represented as a sequence of digits, convert it to base **b**. +Implement general base conversion. +Given a number in base **a**, represented as a sequence of digits, convert it to base **b**. ## Note - Try to implement the conversion yourself. Do not use something else to perform the conversion for you. -## About [Positional Notation](https://en.wikipedia.org/wiki/Positional_notation) +## About [Positional Notation][positional-notation] -In positional notation, a number in base **b** can be understood as a linear -combination of powers of **b**. +In positional notation, a number in base **b** can be understood as a linear combination of powers of **b**. The number 42, *in base 10*, means: -(4 \* 10^1) + (2 \* 10^0) +`(4 * 10^1) + (2 * 10^0)` The number 101010, *in base 2*, means: -(1 \* 2^5) + (0 \* 2^4) + (1 \* 2^3) + (0 \* 2^2) + (1 \* 2^1) + (0 \* 2^0) +`(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)` The number 1120, *in base 3*, means: -(1 \* 3^3) + (1 \* 3^2) + (2 \* 3^1) + (0 \* 3^0) +`(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)` I think you got the idea! *Yes. Those three numbers above are exactly the same. Congratulations!* + +[positional-notation]: https://en.wikipedia.org/wiki/Positional_notation diff --git a/exercises/practice/allergies/.docs/instructions.md b/exercises/practice/allergies/.docs/instructions.md index e89b869721..a139492096 100644 --- a/exercises/practice/allergies/.docs/instructions.md +++ b/exercises/practice/allergies/.docs/instructions.md @@ -2,29 +2,26 @@ Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies. -An allergy test produces a single numeric score which contains the -information about all the allergies the person has (that they were -tested for). +An allergy test produces a single numeric score which contains the information about all the allergies the person has (that they were tested for). The list of items (and their value) that were tested are: -* eggs (1) -* peanuts (2) -* shellfish (4) -* strawberries (8) -* tomatoes (16) -* chocolate (32) -* pollen (64) -* cats (128) +- eggs (1) +- peanuts (2) +- shellfish (4) +- strawberries (8) +- tomatoes (16) +- chocolate (32) +- pollen (64) +- cats (128) So if Tom is allergic to peanuts and chocolate, he gets a score of 34. Now, given just that score of 34, your program should be able to say: -* Whether Tom is allergic to any one of those allergens listed above. -* All the allergens Tom is allergic to. +- Whether Tom is allergic to any one of those allergens listed above. +- All the allergens Tom is allergic to. -Note: a given score may include allergens **not** listed above (i.e. -allergens that score 256, 512, 1024, etc.). Your program should -ignore those components of the score. For example, if the allergy -score is 257, your program should only report the eggs (1) allergy. +Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.). +Your program should ignore those components of the score. +For example, if the allergy score is 257, your program should only report the eggs (1) allergy. diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md index 6936c192d5..649576ec7e 100644 --- a/exercises/practice/alphametics/.docs/instructions.md +++ b/exercises/practice/alphametics/.docs/instructions.md @@ -2,8 +2,7 @@ Write a function to solve alphametics puzzles. -[Alphametics](https://en.wikipedia.org/wiki/Alphametics) is a puzzle where -letters in words are replaced with numbers. +[Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers. For example `SEND + MORE = MONEY`: @@ -23,10 +22,10 @@ Replacing these with valid numbers gives: 1 0 6 5 2 ``` -This is correct because every letter is replaced by a different number and the -words, translated into numbers, then make a valid sum. +This is correct because every letter is replaced by a different number and the words, translated into numbers, then make a valid sum. -Each letter must represent a different digit, and the leading digit of -a multi-digit number must not be zero. +Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero. Write a function to solve alphametics puzzles. + +[alphametics]: https://en.wikipedia.org/wiki/Alphametics diff --git a/exercises/practice/armstrong-numbers/.docs/instructions.md b/exercises/practice/armstrong-numbers/.docs/instructions.md index 452a996fb1..744cfbe7fa 100644 --- a/exercises/practice/armstrong-numbers/.docs/instructions.md +++ b/exercises/practice/armstrong-numbers/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -An [Armstrong number](https://en.wikipedia.org/wiki/Narcissistic_number) is a number that is the sum of its own digits each raised to the power of the number of digits. +An [Armstrong number][armstrong-number] is a number that is the sum of its own digits each raised to the power of the number of digits. For example: @@ -10,3 +10,5 @@ For example: - 154 is *not* an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` Write some code to determine whether a number is an Armstrong number. + +[armstrong-number]: https://en.wikipedia.org/wiki/Narcissistic_number diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md index 328f7cbd14..21ca2ce0aa 100644 --- a/exercises/practice/atbash-cipher/.docs/instructions.md +++ b/exercises/practice/atbash-cipher/.docs/instructions.md @@ -2,10 +2,8 @@ Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East. -The Atbash cipher is a simple substitution cipher that relies on -transposing all the letters in the alphabet such that the resulting -alphabet is backwards. The first letter is replaced with the last -letter, the second with the second-last, and so on. +The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. +The first letter is replaced with the last letter, the second with the second-last, and so on. An Atbash cipher for the Latin alphabet would be as follows: @@ -14,13 +12,12 @@ Plain: abcdefghijklmnopqrstuvwxyz Cipher: zyxwvutsrqponmlkjihgfedcba ``` -It is a very weak cipher because it only has one possible key, and it is -a simple mono-alphabetic substitution cipher. However, this may not have -been an issue in the cipher's time. +It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher. +However, this may not have been an issue in the cipher's time. -Ciphertext is written out in groups of fixed length, the traditional group size -being 5 letters, leaving numbers unchanged, and punctuation is excluded. +Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded. This is to make it harder to guess things based on word boundaries. +All text will be encoded as lowercase letters. ## Examples diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md index d1bb8af0f7..f536fdbb73 100644 --- a/exercises/practice/bank-account/.docs/instructions.md +++ b/exercises/practice/bank-account/.docs/instructions.md @@ -1,14 +1,12 @@ # Instructions -Simulate a bank account supporting opening/closing, withdrawals, and deposits -of money. Watch out for concurrent transactions! +Simulate a bank account supporting opening/closing, withdrawals, and deposits of money. +Watch out for concurrent transactions! -A bank account can be accessed in multiple ways. Clients can make -deposits and withdrawals using the internet, mobile phones, etc. Shops -can charge against the account. +A bank account can be accessed in multiple ways. +Clients can make deposits and withdrawals using the internet, mobile phones, etc. +Shops can charge against the account. -Create an account that can be accessed from multiple threads/processes -(terminology depends on your programming language). +Create an account that can be accessed from multiple threads/processes (terminology depends on your programming language). -It should be possible to close an account; operations against a closed -account must fail. +It should be possible to close an account; operations against a closed account must fail. diff --git a/exercises/practice/beer-song/.docs/instructions.md b/exercises/practice/beer-song/.docs/instructions.md index 57429d8ab0..e909cfe317 100644 --- a/exercises/practice/beer-song/.docs/instructions.md +++ b/exercises/practice/beer-song/.docs/instructions.md @@ -305,17 +305,3 @@ Take it down and pass it around, no more bottles of beer on the wall. No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. ``` - -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -* Remove as much duplication as you possibly can. -* Optimize for readability, even if it means introducing duplication. -* If you've removed all the duplication, do you have a lot of - conditionals? Try replacing the conditionals with polymorphism, if it - applies in this language. How readable is it? - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? diff --git a/exercises/practice/binary-search-tree/.docs/instructions.md b/exercises/practice/binary-search-tree/.docs/instructions.md index ba3c42eb67..c9bbba5b96 100644 --- a/exercises/practice/binary-search-tree/.docs/instructions.md +++ b/exercises/practice/binary-search-tree/.docs/instructions.md @@ -2,29 +2,22 @@ Insert and search for numbers in a binary tree. -When we need to represent sorted data, an array does not make a good -data structure. - -Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes -`[1, 3, 4, 5, 2]` now we must sort the entire array again! We can -improve on this by realizing that we only need to make space for the new -item `[1, nil, 3, 4, 5]`, and then adding the item in the space we -added. But this still requires us to shift many elements down by one. - -Binary Search Trees, however, can operate on sorted data much more -efficiently. - -A binary search tree consists of a series of connected nodes. Each node -contains a piece of data (e.g. the number 3), a variable named `left`, -and a variable named `right`. The `left` and `right` variables point at -`nil`, or other nodes. Since these other nodes in turn have other nodes -beneath them, we say that the left and right variables are pointing at -subtrees. All data in the left subtree is less than or equal to the -current node's data, and all data in the right subtree is greater than -the current node's data. - -For example, if we had a node containing the data 4, and we added the -data 2, our tree would look like this: +When we need to represent sorted data, an array does not make a good data structure. + +Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes `[1, 3, 4, 5, 2]`. +Now we must sort the entire array again! +We can improve on this by realizing that we only need to make space for the new item `[1, nil, 3, 4, 5]`, and then adding the item in the space we added. +But this still requires us to shift many elements down by one. + +Binary Search Trees, however, can operate on sorted data much more efficiently. + +A binary search tree consists of a series of connected nodes. +Each node contains a piece of data (e.g. the number 3), a variable named `left`, and a variable named `right`. +The `left` and `right` variables point at `nil`, or other nodes. +Since these other nodes in turn have other nodes beneath them, we say that the left and right variables are pointing at subtrees. +All data in the left subtree is less than or equal to the current node's data, and all data in the right subtree is greater than the current node's data. + +For example, if we had a node containing the data 4, and we added the data 2, our tree would look like this: 4 / diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index 4dcaba726a..b67705406f 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -2,34 +2,23 @@ Implement a binary search algorithm. -Searching a sorted collection is a common task. A dictionary is a sorted -list of word definitions. Given a word, one can find its definition. A -telephone book is a sorted list of people's names, addresses, and -telephone numbers. Knowing someone's name allows one to quickly find -their telephone number and address. +Searching a sorted collection is a common task. +A dictionary is a sorted list of word definitions. +Given a word, one can find its definition. +A telephone book is a sorted list of people's names, addresses, and telephone numbers. +Knowing someone's name allows one to quickly find their telephone number and address. -If the list to be searched contains more than a few items (a dozen, say) -a binary search will require far fewer comparisons than a linear search, -but it imposes the requirement that the list be sorted. +If the list to be searched contains more than a few items (a dozen, say) a binary search will require far fewer comparisons than a linear search, but it imposes the requirement that the list be sorted. -In computer science, a binary search or half-interval search algorithm -finds the position of a specified input value (the search "key") within -an array sorted by key value. +In computer science, a binary search or half-interval search algorithm finds the position of a specified input value (the search "key") within an array sorted by key value. -In each step, the algorithm compares the search key value with the key -value of the middle element of the array. +In each step, the algorithm compares the search key value with the key value of the middle element of the array. -If the keys match, then a matching element has been found and its index, -or position, is returned. +If the keys match, then a matching element has been found and its index, or position, is returned. -Otherwise, if the search key is less than the middle element's key, then -the algorithm repeats its action on the sub-array to the left of the -middle element or, if the search key is greater, on the sub-array to the -right. +Otherwise, if the search key is less than the middle element's key, then the algorithm repeats its action on the sub-array to the left of the middle element or, if the search key is greater, on the sub-array to the right. -If the remaining array to be searched is empty, then the key cannot be -found in the array and a special "not found" indication is returned. +If the remaining array to be searched is empty, then the key cannot be found in the array and a special "not found" indication is returned. -A binary search halves the number of items to check with each iteration, -so locating an item (or determining its absence) takes logarithmic time. +A binary search halves the number of items to check with each iteration, so locating an item (or determining its absence) takes logarithmic time. A binary search is a dichotomic divide and conquer search algorithm. diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md index edddb1413d..7888c9b76f 100644 --- a/exercises/practice/bob/.docs/instructions.md +++ b/exercises/practice/bob/.docs/instructions.md @@ -1,6 +1,7 @@ # Instructions -Bob is a lackadaisical teenager. In conversation, his responses are very limited. +Bob is a lackadaisical teenager. +In conversation, his responses are very limited. Bob answers 'Sure.' if you ask him a question, such as "How are you?". @@ -8,8 +9,7 @@ He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals). He answers 'Calm down, I know what I'm doing!' if you yell a question at him. -He says 'Fine. Be that way!' if you address him without actually saying -anything. +He says 'Fine. Be that way!' if you address him without actually saying anything. He answers 'Whatever.' to anything else. diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md index cce9deea01..341ad01fbd 100644 --- a/exercises/practice/book-store/.docs/instructions.md +++ b/exercises/practice/book-store/.docs/instructions.md @@ -1,12 +1,10 @@ # Instructions -To try and encourage more sales of different books from a popular 5 book -series, a bookshop has decided to offer discounts on multiple book purchases. +To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts on multiple book purchases. One copy of any of the five books costs $8. -If, however, you buy two different books, you get a 5% -discount on those two books. +If, however, you buy two different books, you get a 5% discount on those two books. If you buy 3 different books, you get a 10% discount. @@ -14,14 +12,9 @@ If you buy 4 different books, you get a 20% discount. If you buy all 5, you get a 25% discount. -Note: that if you buy four books, of which 3 are -different titles, you get a 10% discount on the 3 that -form part of a set, but the fourth book still costs $8. +Note: that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8. -Your mission is to write a piece of code to calculate the -price of any conceivable shopping basket (containing only -books of the same series), giving as big a discount as -possible. +Your mission is to write a piece of code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible. For example, how much does this basket of books cost? diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md index be9b27faf0..48a2fedcf4 100644 --- a/exercises/practice/bowling/.docs/instructions.md +++ b/exercises/practice/bowling/.docs/instructions.md @@ -2,29 +2,24 @@ Score a bowling game. -Bowling is a game where players roll a heavy ball to knock down pins -arranged in a triangle. Write code to keep track of the score -of a game of bowling. +Bowling is a game where players roll a heavy ball to knock down pins arranged in a triangle. +Write code to keep track of the score of a game of bowling. ## Scoring Bowling -The game consists of 10 frames. A frame is composed of one or two ball -throws with 10 pins standing at frame initialization. There are three -cases for the tabulation of a frame. +The game consists of 10 frames. +A frame is composed of one or two ball throws with 10 pins standing at frame initialization. +There are three cases for the tabulation of a frame. -* An open frame is where a score of less than 10 is recorded for the - frame. In this case the score for the frame is the number of pins - knocked down. +- An open frame is where a score of less than 10 is recorded for the frame. + In this case the score for the frame is the number of pins knocked down. -* A spare is where all ten pins are knocked down by the second - throw. The total value of a spare is 10 plus the number of pins - knocked down in their next throw. +- A spare is where all ten pins are knocked down by the second throw. + The total value of a spare is 10 plus the number of pins knocked down in their next throw. -* A strike is where all ten pins are knocked down by the first - throw. The total value of a strike is 10 plus the number of pins - knocked down in the next two throws. If a strike is immediately - followed by a second strike, then the value of the first strike - cannot be determined until the ball is thrown one more time. +- A strike is where all ten pins are knocked down by the first throw. + The total value of a strike is 10 plus the number of pins knocked down in the next two throws. + If a strike is immediately followed by a second strike, then the value of the first strike cannot be determined until the ball is thrown one more time. Here is a three frame example: @@ -40,11 +35,11 @@ Frame 3 is (9 + 0) = 9 This means the current running total is 48. -The tenth frame in the game is a special case. If someone throws a -strike or a spare then they get a fill ball. Fill balls exist to -calculate the total of the 10th frame. Scoring a strike or spare on -the fill ball does not give the player more fill balls. The total -value of the 10th frame is the total number of pins knocked down. +The tenth frame in the game is a special case. +If someone throws a strike or a spare then they get a fill ball. +Fill balls exist to calculate the total of the 10th frame. +Scoring a strike or spare on the fill ball does not give the player more fill balls. +The total value of the 10th frame is the total number of pins knocked down. For a tenth frame of X1/ (strike and a spare), the total value is 20. @@ -52,10 +47,10 @@ For a tenth frame of XXX (three strikes), the total value is 30. ## Requirements -Write code to keep track of the score of a game of bowling. It should -support two operations: +Write code to keep track of the score of a game of bowling. +It should support two operations: -* `roll(pins : int)` is called each time the player rolls a ball. The - argument is the number of pins knocked down. -* `score() : int` is called only at the very end of the game. It - returns the total score for that game. +- `roll(pins : int)` is called each time the player rolls a ball. + The argument is the number of pins knocked down. +- `score() : int` is called only at the very end of the game. + It returns the total score for that game. diff --git a/exercises/practice/change/.docs/instructions.md b/exercises/practice/change/.docs/instructions.md index 59f4f4f90d..30fa567750 100644 --- a/exercises/practice/change/.docs/instructions.md +++ b/exercises/practice/change/.docs/instructions.md @@ -1,14 +1,11 @@ # Instructions -Correctly determine the fewest number of coins to be given to a customer such -that the sum of the coins' value would equal the correct amount of change. +Correctly determine the fewest number of coins to be given to a customer such that the sum of the coins' value would equal the correct amount of change. ## For example -- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5) - and one dime (10) or [5, 10] -- An input of 40 with [1, 5, 10, 25, 100] should return one nickel (5) - and one dime (10) and one quarter (25) or [5, 10, 25] +- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) or [5, 10] +- An input of 40 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) and one quarter (25) or [5, 10, 25] ## Edge cases diff --git a/exercises/practice/circular-buffer/.docs/instructions.md b/exercises/practice/circular-buffer/.docs/instructions.md index e9b00b91dc..3487a0f614 100644 --- a/exercises/practice/circular-buffer/.docs/instructions.md +++ b/exercises/practice/circular-buffer/.docs/instructions.md @@ -1,26 +1,22 @@ # Instructions -A circular buffer, cyclic buffer or ring buffer is a data structure that -uses a single, fixed-size buffer as if it were connected end-to-end. +A circular buffer, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. -A circular buffer first starts empty and of some predefined length. For -example, this is a 7-element buffer: +A circular buffer first starts empty and of some predefined length. +For example, this is a 7-element buffer: [ ][ ][ ][ ][ ][ ][ ] -Assume that a 1 is written into the middle of the buffer (exact starting -location does not matter in a circular buffer): +Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer): [ ][ ][ ][1][ ][ ][ ] -Then assume that two more elements are added — 2 & 3 — which get -appended after the 1: +Then assume that two more elements are added — 2 & 3 — which get appended after the 1: [ ][ ][ ][1][2][3][ ] -If two elements are then removed from the buffer, the oldest values -inside the buffer are removed. The two elements removed, in this case, -are 1 & 2, leaving the buffer with just a 3: +If two elements are then removed from the buffer, the oldest values inside the buffer are removed. +The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3: [ ][ ][ ][ ][ ][3][ ] @@ -28,24 +24,19 @@ If the buffer has 7 elements then it is completely full: [5][6][7][8][9][3][4] -When the buffer is full an error will be raised, alerting the client -that further writes are blocked until a slot becomes free. +When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free. -When the buffer is full, the client can opt to overwrite the oldest -data with a forced write. In this case, two more elements — A & B — -are added and they overwrite the 3 & 4: +When the buffer is full, the client can opt to overwrite the oldest data with a forced write. +In this case, two more elements — A & B — are added and they overwrite the 3 & 4: [5][6][7][8][9][A][B] -3 & 4 have been replaced by A & B making 5 now the oldest data in the -buffer. Finally, if two elements are removed then what would be -returned is 5 & 6 yielding the buffer: +3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer. +Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer: [ ][ ][7][8][9][A][B] -Because there is space available, if the client again uses overwrite -to store C & D then the space where 5 & 6 were stored previously will -be used not the location of 7 & 8. 7 is still the oldest element and -the buffer is once again full. +Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8. +7 is still the oldest element and the buffer is once again full. [C][D][7][8][9][A][B] diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.md b/exercises/practice/collatz-conjecture/.docs/instructions.md index f8c76e7f11..ba060483e4 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.md @@ -2,10 +2,11 @@ The Collatz Conjecture or 3x+1 problem can be summarized as follows: -Take any positive integer n. If n is even, divide n by 2 to get n / 2. If n is -odd, multiply n by 3 and add 1 to get 3n + 1. Repeat the process indefinitely. -The conjecture states that no matter which number you start with, you will -always reach 1 eventually. +Take any positive integer n. +If n is even, divide n by 2 to get n / 2. +If n is odd, multiply n by 3 and add 1 to get 3n + 1. +Repeat the process indefinitely. +The conjecture states that no matter which number you start with, you will always reach 1 eventually. Given a number n, return the number of steps required to reach 1. @@ -24,4 +25,5 @@ Starting with n = 12, the steps would be as follows: 8. 2 9. 1 -Resulting in 9 steps. So for input n = 12, the return value would be 9. +Resulting in 9 steps. +So for input n = 12, the return value would be 9. diff --git a/exercises/practice/connect/.docs/instructions.md b/exercises/practice/connect/.docs/instructions.md index 2fa003a835..7f34bfa817 100644 --- a/exercises/practice/connect/.docs/instructions.md +++ b/exercises/practice/connect/.docs/instructions.md @@ -2,19 +2,14 @@ Compute the result for a game of Hex / Polygon. -The abstract boardgame known as -[Hex](https://en.wikipedia.org/wiki/Hex_%28board_game%29) / Polygon / -CON-TAC-TIX is quite simple in rules, though complex in practice. Two players -place stones on a parallelogram with hexagonal fields. The player to connect his/her -stones to the opposite side first wins. The four sides of the parallelogram are -divided between the two players (i.e. one player gets assigned a side and the -side directly opposite it and the other player gets assigned the two other -sides). +The abstract boardgame known as [Hex][hex] / Polygon / CON-TAC-TIX is quite simple in rules, though complex in practice. +Two players place stones on a parallelogram with hexagonal fields. +The player to connect his/her stones to the opposite side first wins. +The four sides of the parallelogram are divided between the two players (i.e. one player gets assigned a side and the side directly opposite it and the other player gets assigned the two other sides). -Your goal is to build a program that given a simple representation of a board -computes the winner (or lack thereof). Note that all games need not be "fair". -(For example, players may have mismatched piece counts or the game's board might -have a different width and height.) +Your goal is to build a program that given a simple representation of a board computes the winner (or lack thereof). +Note that all games need not be "fair". +(For example, players may have mismatched piece counts or the game's board might have a different width and height.) The boards look like this: @@ -26,6 +21,7 @@ The boards look like this: X O O O X ``` -"Player `O`" plays from top to bottom, "Player `X`" plays from left to right. In -the above example `O` has made a connection from left to right but nobody has -won since `O` didn't connect top and bottom. +"Player `O`" plays from top to bottom, "Player `X`" plays from left to right. +In the above example `O` has made a connection from left to right but nobody has won since `O` didn't connect top and bottom. + +[hex]: https://en.wikipedia.org/wiki/Hex_%28board_game%29 diff --git a/exercises/practice/crypto-square/.docs/instructions.md b/exercises/practice/crypto-square/.docs/instructions.md index 8a489f6e2c..6c3826ee55 100644 --- a/exercises/practice/crypto-square/.docs/instructions.md +++ b/exercises/practice/crypto-square/.docs/instructions.md @@ -4,11 +4,10 @@ Implement the classic method for composing secret messages called a square code. Given an English text, output the encoded version of that text. -First, the input is normalized: the spaces and punctuation are removed -from the English text and the message is down-cased. +First, the input is normalized: the spaces and punctuation are removed from the English text and the message is down-cased. -Then, the normalized characters are broken into rows. These rows can be -regarded as forming a rectangle when printed with intervening newlines. +Then, the normalized characters are broken into rows. +These rows can be regarded as forming a rectangle when printed with intervening newlines. For example, the sentence @@ -22,18 +21,16 @@ is normalized to: "ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots" ``` -The plaintext should be organized in to a rectangle. The size of the -rectangle should be decided by the length of the message. +The plaintext should be organized into a rectangle as square as possible. +The size of the rectangle should be decided by the length of the message. -If `c` is the number of columns and `r` is the number of rows, then for -the rectangle `r` x `c` find the smallest possible integer `c` such that: +If `c` is the number of columns and `r` is the number of rows, then for the rectangle `r` x `c` find the smallest possible integer `c` such that: -- `r * c >= length(message)`, +- `r * c >= length of message`, - and `c >= r`, - and `c - r <= 1`. -Our normalized text is 54 characters long, dictating a rectangle with -`c = 8` and `r = 7`: +Our normalized text is 54 characters long, dictating a rectangle with `c = 8` and `r = 7`: ```text "ifmanwas" @@ -45,8 +42,7 @@ Our normalized text is 54 characters long, dictating a rectangle with "sroots " ``` -The coded message is obtained by reading down the columns going left to -right. +The coded message is obtained by reading down the columns going left to right. The message above is coded as: @@ -54,17 +50,14 @@ The message above is coded as: "imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau" ``` -Output the encoded text in chunks that fill perfect rectangles `(r X c)`, -with `c` chunks of `r` length, separated by spaces. For phrases that are -`n` characters short of the perfect rectangle, pad each of the last `n` -chunks with a single trailing space. +Output the encoded text in chunks that fill perfect rectangles `(r X c)`, with `c` chunks of `r` length, separated by spaces. +For phrases that are `n` characters short of the perfect rectangle, pad each of the last `n` chunks with a single trailing space. ```text "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " ``` -Notice that were we to stack these, we could visually decode the -ciphertext back in to the original message: +Notice that were we to stack these, we could visually decode the ciphertext back in to the original message: ```text "imtgdvs" diff --git a/exercises/practice/custom-set/.docs/instructions.md b/exercises/practice/custom-set/.docs/instructions.md index e4931b058b..33b90e28d7 100644 --- a/exercises/practice/custom-set/.docs/instructions.md +++ b/exercises/practice/custom-set/.docs/instructions.md @@ -2,7 +2,6 @@ Create a custom set type. -Sometimes it is necessary to define a custom data structure of some -type, like a set. In this exercise you will define your own set. How it -works internally doesn't matter, as long as it behaves like a set of -unique elements. +Sometimes it is necessary to define a custom data structure of some type, like a set. +In this exercise you will define your own set. +How it works internally doesn't matter, as long as it behaves like a set of unique elements. diff --git a/exercises/practice/darts/.docs/instructions.md b/exercises/practice/darts/.docs/instructions.md index b2cddf3915..7af7428a06 100644 --- a/exercises/practice/darts/.docs/instructions.md +++ b/exercises/practice/darts/.docs/instructions.md @@ -2,16 +2,22 @@ Write a function that returns the earned points in a single toss of a Darts game. -[Darts](https://en.wikipedia.org/wiki/Darts) is a game where players -throw darts at a [target](https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg). +[Darts][darts] is a game where players throw darts at a [target][darts-target]. In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands: -* If the dart lands outside the target, player earns no points (0 points). -* If the dart lands in the outer circle of the target, player earns 1 point. -* If the dart lands in the middle circle of the target, player earns 5 points. -* If the dart lands in the inner circle of the target, player earns 10 points. +- If the dart lands outside the target, player earns no points (0 points). +- If the dart lands in the outer circle of the target, player earns 1 point. +- If the dart lands in the middle circle of the target, player earns 5 points. +- If the dart lands in the inner circle of the target, player earns 10 points. -The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. Of course, they are all centered at the same point (that is, the circles are [concentric](http://mathworld.wolfram.com/ConcentricCircles.html)) defined by the coordinates (0, 0). +The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. +Of course, they are all centered at the same point (that is, the circles are [concentric][] defined by the coordinates (0, 0). -Write a function that given a point in the target (defined by its [Cartesian coordinates](https://www.mathsisfun.com/data/cartesian-coordinates.html) `x` and `y`, where `x` and `y` are [real](https://www.mathsisfun.com/numbers/real-numbers.html)), returns the correct amount earned by a dart landing at that point. +Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point. + +[darts]: https://en.wikipedia.org/wiki/Darts +[darts-target]: https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg +[concentric]: https://mathworld.wolfram.com/ConcentricCircles.html +[cartesian-coordinates]: https://www.mathsisfun.com/data/cartesian-coordinates.html +[real-numbers]: https://www.mathsisfun.com/numbers/real-numbers.html diff --git a/exercises/practice/diamond/.docs/instructions.md b/exercises/practice/diamond/.docs/instructions.md index 1de7016f09..3034802feb 100644 --- a/exercises/practice/diamond/.docs/instructions.md +++ b/exercises/practice/diamond/.docs/instructions.md @@ -1,22 +1,21 @@ # Instructions -The diamond kata takes as its input a letter, and outputs it in a diamond -shape. Given a letter, it prints a diamond starting with 'A', with the -supplied letter at the widest point. +The diamond kata takes as its input a letter, and outputs it in a diamond shape. +Given a letter, it prints a diamond starting with 'A', with the supplied letter at the widest point. ## Requirements -* The first row contains one 'A'. -* The last row contains one 'A'. -* All rows, except the first and last, have exactly two identical letters. -* All rows have as many trailing spaces as leading spaces. (This might be 0). -* The diamond is horizontally symmetric. -* The diamond is vertically symmetric. -* The diamond has a square shape (width equals height). -* The letters form a diamond shape. -* The top half has the letters in ascending order. -* The bottom half has the letters in descending order. -* The four corners (containing the spaces) are triangles. +- The first row contains one 'A'. +- The last row contains one 'A'. +- All rows, except the first and last, have exactly two identical letters. +- All rows have as many trailing spaces as leading spaces. (This might be 0). +- The diamond is horizontally symmetric. +- The diamond is vertically symmetric. +- The diamond has a square shape (width equals height). +- The letters form a diamond shape. +- The top half has the letters in ascending order. +- The bottom half has the letters in descending order. +- The four corners (containing the spaces) are triangles. ## Examples diff --git a/exercises/practice/difference-of-squares/.docs/instructions.md b/exercises/practice/difference-of-squares/.docs/instructions.md index c3999e86ab..39c38b5094 100644 --- a/exercises/practice/difference-of-squares/.docs/instructions.md +++ b/exercises/practice/difference-of-squares/.docs/instructions.md @@ -8,10 +8,7 @@ The square of the sum of the first ten natural numbers is The sum of the squares of the first ten natural numbers is 1² + 2² + ... + 10² = 385. -Hence the difference between the square of the sum of the first -ten natural numbers and the sum of the squares of the first ten -natural numbers is 3025 - 385 = 2640. +Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640. -You are not expected to discover an efficient solution to this yourself from -first principles; research is allowed, indeed, encouraged. Finding the best -algorithm for the problem is a key skill in software engineering. +You are not expected to discover an efficient solution to this yourself from first principles; research is allowed, indeed, encouraged. +Finding the best algorithm for the problem is a key skill in software engineering. diff --git a/exercises/practice/diffie-hellman/.docs/instructions.md b/exercises/practice/diffie-hellman/.docs/instructions.md index 5a6172f1ff..9f1c85e312 100644 --- a/exercises/practice/diffie-hellman/.docs/instructions.md +++ b/exercises/practice/diffie-hellman/.docs/instructions.md @@ -2,9 +2,8 @@ Diffie-Hellman key exchange. -Alice and Bob use Diffie-Hellman key exchange to share secrets. They -start with prime numbers, pick private keys, generate and share public -keys, and then generate a shared secret key. +Alice and Bob use Diffie-Hellman key exchange to share secrets. +They start with prime numbers, pick private keys, generate and share public keys, and then generate a shared secret key. ## Step 0 @@ -12,8 +11,8 @@ The test program supplies prime numbers p and g. ## Step 1 -Alice picks a private key, a, greater than 1 and less than p. Bob does -the same to pick a private key b. +Alice picks a private key, a, greater than 1 and less than p. +Bob does the same to pick a private key b. ## Step 2 @@ -21,12 +20,12 @@ Alice calculates a public key A. A = gᵃ mod p -Using the same p and g, Bob similarly calculates a public key B from his -private key b. +Using the same p and g, Bob similarly calculates a public key B from his private key b. ## Step 3 -Alice and Bob exchange public keys. Alice calculates secret key s. +Alice and Bob exchange public keys. +Alice calculates secret key s. s = Bᵃ mod p @@ -34,5 +33,5 @@ Bob calculates s = Aᵇ mod p -The calculations produce the same result! Alice and Bob now share -secret s. +The calculations produce the same result! +Alice and Bob now share secret s. diff --git a/exercises/practice/dnd-character/.docs/instructions.md b/exercises/practice/dnd-character/.docs/instructions.md index 53d9f98517..b0a603591e 100644 --- a/exercises/practice/dnd-character/.docs/instructions.md +++ b/exercises/practice/dnd-character/.docs/instructions.md @@ -1,33 +1,31 @@ # Instructions -For a game of [Dungeons & Dragons][DND], each player starts by generating a -character they can play with. This character has, among other things, six -abilities; strength, dexterity, constitution, intelligence, wisdom and -charisma. These six abilities have scores that are determined randomly. You -do this by rolling four 6-sided dice and record the sum of the largest three -dice. You do this six times, once for each ability. +For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with. +This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma. +These six abilities have scores that are determined randomly. +You do this by rolling four 6-sided dice and record the sum of the largest three dice. +You do this six times, once for each ability. -Your character's initial hitpoints are 10 + your character's constitution -modifier. You find your character's constitution modifier by subtracting 10 -from your character's constitution, divide by 2 and round down. +Your character's initial hitpoints are 10 + your character's constitution modifier. +You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down. Write a random character generator that follows the rules above. For example, the six throws of four dice may look like: -* 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength. -* 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity. -* 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution. -* 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence. -* 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom. -* 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma. +- 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength. +- 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity. +- 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution. +- 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence. +- 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom. +- 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma. Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6. ## Notes -Most programming languages feature (pseudo-)random generators, but few -programming languages are designed to roll dice. One such language is [Troll]. +Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. +One such language is [Troll][troll]. -[DND]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons -[Troll]: http://hjemmesider.diku.dk/~torbenm/Troll/ +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons +[troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html diff --git a/exercises/practice/dominoes/.docs/instructions.md b/exercises/practice/dominoes/.docs/instructions.md index 808aa42423..1ced9f6448 100644 --- a/exercises/practice/dominoes/.docs/instructions.md +++ b/exercises/practice/dominoes/.docs/instructions.md @@ -2,14 +2,12 @@ Make a chain of dominoes. -Compute a way to order a given set of dominoes in such a way that they form a -correct domino chain (the dots on one half of a stone match the dots on the -neighboring half of an adjacent stone) and that dots on the halves of the -stones which don't have a neighbor (the first and last stone) match each other. +Compute a way to order a given set of dominoes in such a way that they form a correct domino chain (the dots on one half of a stone match the dots on the neighboring half of an adjacent stone) and that dots on the halves of the stones which don't have a neighbor (the first and last stone) match each other. For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same. -For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. 4 != 3 +For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. +4 != 3 Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used. diff --git a/exercises/practice/dot-dsl/.docs/instructions.md b/exercises/practice/dot-dsl/.docs/instructions.md index f61f3f0b4d..9230547ea5 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.md +++ b/exercises/practice/dot-dsl/.docs/instructions.md @@ -1,16 +1,12 @@ # Instructions -A [Domain Specific Language -(DSL)](https://en.wikipedia.org/wiki/Domain-specific_language) is a -small language optimized for a specific domain. Since a DSL is -targeted, it can greatly impact productivity/understanding by allowing the -writer to declare *what* they want rather than *how*. +A [Domain Specific Language (DSL)][dsl] is a small language optimized for a specific domain. +Since a DSL is targeted, it can greatly impact productivity/understanding by allowing the writer to declare *what* they want rather than *how*. One problem area where they are applied are complex customizations/configurations. -For example the [DOT language](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) allows -you to write a textual description of a graph which is then transformed into a picture by one of -the [Graphviz](http://graphviz.org/) tools (such as `dot`). A simple graph looks like this: +For example the [DOT language][dot-language] allows you to write a textual description of a graph which is then transformed into a picture by one of the [Graphviz][graphviz] tools (such as `dot`). +A simple graph looks like this: graph { graph [bgcolor="yellow"] @@ -19,15 +15,16 @@ the [Graphviz](http://graphviz.org/) tools (such as `dot`). A simple graph looks a -- b [color="green"] } -Putting this in a file `example.dot` and running `dot example.dot -T png --o example.png` creates an image `example.png` with red and blue circle -connected by a green line on a yellow background. +Putting this in a file `example.dot` and running `dot example.dot -T png -o example.png` creates an image `example.png` with red and blue circle connected by a green line on a yellow background. Write a Domain Specific Language similar to the Graphviz dot language. -Our DSL is similar to the Graphviz dot language in that our DSL will be used -to create graph data structures. However, unlike the DOT Language, our DSL will -be an internal DSL for use only in our language. +Our DSL is similar to the Graphviz dot language in that our DSL will be used to create graph data structures. +However, unlike the DOT Language, our DSL will be an internal DSL for use only in our language. -More information about the difference between internal and external DSLs can be -found [here](https://martinfowler.com/bliki/DomainSpecificLanguage.html). +More information about the difference between internal and external DSLs can be found [here][fowler-dsl]. + +[dsl]: https://en.wikipedia.org/wiki/Domain-specific_language +[dot-language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language) +[graphviz]: https://graphviz.org/ +[fowler-dsl]: https://martinfowler.com/bliki/DomainSpecificLanguage.html diff --git a/exercises/practice/etl/.docs/instructions.md b/exercises/practice/etl/.docs/instructions.md index ff96906c6b..fffe64f201 100644 --- a/exercises/practice/etl/.docs/instructions.md +++ b/exercises/practice/etl/.docs/instructions.md @@ -4,12 +4,10 @@ We are going to do the `Transform` step of an Extract-Transform-Load. ## ETL -Extract-Transform-Load (ETL) is a fancy way of saying, "We have some crufty, legacy data over in this system, and now we need it in this shiny new system over here, so -we're going to migrate this." +Extract-Transform-Load (ETL) is a fancy way of saying, "We have some crufty, legacy data over in this system, and now we need it in this shiny new system over here, so we're going to migrate this." -(Typically, this is followed by, "We're only going to need to run this -once." That's then typically followed by much forehead slapping and -moaning about how stupid we could possibly be.) +(Typically, this is followed by, "We're only going to need to run this once." +That's then typically followed by much forehead slapping and moaning about how stupid we could possibly be.) ## The goal @@ -25,10 +23,8 @@ The old system stored a list of letters per score: - 8 points: "J", "X", - 10 points: "Q", "Z", -The shiny new Scrabble system instead stores the score per letter, which -makes it much faster and easier to calculate the score for a word. It -also stores the letters in lower-case regardless of the case of the -input letters: +The shiny new Scrabble system instead stores the score per letter, which makes it much faster and easier to calculate the score for a word. +It also stores the letters in lower-case regardless of the case of the input letters: - "a" is worth 1 point. - "b" is worth 3 points. @@ -36,12 +32,9 @@ input letters: - "d" is worth 2 points. - Etc. -Your mission, should you choose to accept it, is to transform the legacy data -format to the shiny new format. +Your mission, should you choose to accept it, is to transform the legacy data format to the shiny new format. ## Notes -A final note about scoring, Scrabble is played around the world in a -variety of languages, each with its own unique scoring table. For -example, an "E" is scored at 2 in the Māori-language version of the -game while being scored at 4 in the Hawaiian-language version. +A final note about scoring, Scrabble is played around the world in a variety of languages, each with its own unique scoring table. +For example, an "E" is scored at 2 in the Māori-language version of the game while being scored at 4 in the Hawaiian-language version. diff --git a/exercises/practice/flatten-array/.docs/instructions.md b/exercises/practice/flatten-array/.docs/instructions.md index 02b68cdfeb..51bea67909 100644 --- a/exercises/practice/flatten-array/.docs/instructions.md +++ b/exercises/practice/flatten-array/.docs/instructions.md @@ -4,7 +4,7 @@ Take a nested list and return a single flattened list with all values except nil The challenge is to write a function that accepts an arbitrarily-deep nested list-like structure and returns a flattened structure without any nil/null values. -For Example +For example: input: [1,[2,3,null,4],[null],5] diff --git a/exercises/practice/food-chain/.docs/instructions.md b/exercises/practice/food-chain/.docs/instructions.md index 4d9c10b599..125820e321 100644 --- a/exercises/practice/food-chain/.docs/instructions.md +++ b/exercises/practice/food-chain/.docs/instructions.md @@ -2,11 +2,9 @@ Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'. -While you could copy/paste the lyrics, -or read them from a file, this problem is much more -interesting if you approach it algorithmically. +While you could copy/paste the lyrics, or read them from a file, this problem is much more interesting if you approach it algorithmically. -This is a [cumulative song](http://en.wikipedia.org/wiki/Cumulative_song) of unknown origin. +This is a [cumulative song][cumulative-song] of unknown origin. This is one of many common variants. @@ -62,3 +60,5 @@ I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a horse. She's dead, of course! ``` + +[cumulative-song]: https://en.wikipedia.org/wiki/Cumulative_song diff --git a/exercises/practice/forth/.docs/instructions.md b/exercises/practice/forth/.docs/instructions.md index f481b725a6..91ad26e6e9 100644 --- a/exercises/practice/forth/.docs/instructions.md +++ b/exercises/practice/forth/.docs/instructions.md @@ -2,25 +2,22 @@ Implement an evaluator for a very simple subset of Forth. -[Forth](https://en.wikipedia.org/wiki/Forth_%28programming_language%29) -is a stack-based programming language. Implement a very basic evaluator -for a small subset of Forth. +[Forth][forth] +is a stack-based programming language. +Implement a very basic evaluator for a small subset of Forth. Your evaluator has to support the following words: - `+`, `-`, `*`, `/` (integer arithmetic) - `DUP`, `DROP`, `SWAP`, `OVER` (stack manipulation) -Your evaluator also has to support defining new words using the -customary syntax: `: word-name definition ;`. +Your evaluator also has to support defining new words using the customary syntax: `: word-name definition ;`. -To keep things simple the only data type you need to support is signed -integers of at least 16 bits size. +To keep things simple the only data type you need to support is signed integers of at least 16 bits size. -You should use the following rules for the syntax: a number is a -sequence of one or more (ASCII) digits, a word is a sequence of one or -more letters, digits, symbols or punctuation that is not a number. -(Forth probably uses slightly different rules, but this is close -enough.) +You should use the following rules for the syntax: a number is a sequence of one or more (ASCII) digits, a word is a sequence of one or more letters, digits, symbols or punctuation that is not a number. +(Forth probably uses slightly different rules, but this is close enough.) Words are case-insensitive. + +[forth]: https://en.wikipedia.org/wiki/Forth_%28programming_language%29 diff --git a/exercises/practice/gigasecond/.docs/instructions.md b/exercises/practice/gigasecond/.docs/instructions.md index 680870f3a8..41a057c44a 100644 --- a/exercises/practice/gigasecond/.docs/instructions.md +++ b/exercises/practice/gigasecond/.docs/instructions.md @@ -1,6 +1,5 @@ # Instructions -Given a moment, determine the moment that would be after a gigasecond -has passed. +Given a moment, determine the moment that would be after a gigasecond has passed. A gigasecond is 10^9 (1,000,000,000) seconds. diff --git a/exercises/practice/go-counting/.docs/instructions.md b/exercises/practice/go-counting/.docs/instructions.md index d231a09c7a..15fdab20ba 100644 --- a/exercises/practice/go-counting/.docs/instructions.md +++ b/exercises/practice/go-counting/.docs/instructions.md @@ -2,21 +2,17 @@ Count the scored points on a Go board. -In the game of go (also known as baduk, igo, cờ vây and wéiqí) points -are gained by completely encircling empty intersections with your -stones. The encircled intersections of a player are known as its -territory. +In the game of go (also known as baduk, igo, cờ vây and wéiqí) points are gained by completely encircling empty intersections with your stones. +The encircled intersections of a player are known as its territory. -Write a function that determines the territory of each player. You may -assume that any stones that have been stranded in enemy territory have -already been taken off the board. +Write a function that determines the territory of each player. +You may assume that any stones that have been stranded in enemy territory have already been taken off the board. Write a function that determines the territory which includes a specified coordinate. -Multiple empty intersections may be encircled at once and for encircling -only horizontal and vertical neighbors count. In the following diagram -the stones which matter are marked "O" and the stones that don't are -marked "I" (ignored). Empty spaces represent empty intersections. +Multiple empty intersections may be encircled at once and for encircling only horizontal and vertical neighbors count. +In the following diagram the stones which matter are marked "O" and the stones that don't are marked "I" (ignored). +Empty spaces represent empty intersections. ```text +----+ @@ -27,10 +23,9 @@ marked "I" (ignored). Empty spaces represent empty intersections. +----+ ``` -To be more precise an empty intersection is part of a player's territory -if all of its neighbors are either stones of that player or empty -intersections that are part of that player's territory. +To be more precise an empty intersection is part of a player's territory if all of its neighbors are either stones of that player or empty intersections that are part of that player's territory. -For more information see -[wikipedia](https://en.wikipedia.org/wiki/Go_%28game%29) or [Sensei's -Library](http://senseis.xmp.net/). +For more information see [wikipedia][go-wikipedia] or [Sensei's Library][go-sensei]. + +[go-wikipedia]: https://en.wikipedia.org/wiki/Go_%28game%29 +[go-sensei]: https://senseis.xmp.net/ diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md index 012e7add8b..9a63e398d8 100644 --- a/exercises/practice/grade-school/.docs/instructions.md +++ b/exercises/practice/grade-school/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given students' names along with the grade that they are in, create a roster -for the school. +Given students' names along with the grade that they are in, create a roster for the school. In the end, you should be able to: @@ -11,31 +10,12 @@ In the end, you should be able to: - Get a list of all students enrolled in a grade - "Which students are in grade 2?" - "We've only got Jim just now." -- Get a sorted list of all students in all grades. Grades should sort - as 1, 2, 3, etc., and students within a grade should be sorted - alphabetically by name. +- Get a sorted list of all students in all grades. + Grades should sort as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. - "Who all is enrolled in school right now?" - - "Let me think. We have - Anna, Barb, and Charlie in grade 1, - Alex, Peter, and Zoe in grade 2 - and Jim in grade 5. - So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" + - "Let me think. + We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2 and Jim in grade 5. + So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" -Note that all our students only have one name (It's a small town, what -do you want?) and each student cannot be added more than once to a grade or the -roster. -In fact, when a test attempts to add the same student more than once, your -implementation should indicate that this is incorrect. - -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -- If you're working in a language with mutable data structures and your - implementation allows outside code to mutate the school's internal DB - directly, see if you can prevent this. Feel free to introduce additional - tests. - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? +Note that all our students only have one name (It's a small town, what do you want?) and each student cannot be added more than once to a grade or the roster. +In fact, when a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md index d955f12230..df479fc0a1 100644 --- a/exercises/practice/grains/.docs/instructions.md +++ b/exercises/practice/grains/.docs/instructions.md @@ -1,13 +1,11 @@ # Instructions -Calculate the number of grains of wheat on a chessboard given that the number -on each square doubles. +Calculate the number of grains of wheat on a chessboard given that the number on each square doubles. -There once was a wise servant who saved the life of a prince. The king -promised to pay whatever the servant could dream up. Knowing that the -king loved chess, the servant told the king he would like to have grains -of wheat. One grain on the first square of a chess board, with the number -of grains doubling on each successive square. +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chess board, with the number of grains doubling on each successive square. There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). @@ -15,14 +13,3 @@ Write code that shows: - how many grains were on a given square, and - the total number of grains on the chessboard - -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -- Optimize for speed. -- Optimize for readability. - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? diff --git a/exercises/practice/grep/.docs/instructions.md b/exercises/practice/grep/.docs/instructions.md index 602ea29dbb..004f28acd5 100644 --- a/exercises/practice/grep/.docs/instructions.md +++ b/exercises/practice/grep/.docs/instructions.md @@ -1,77 +1,27 @@ # Instructions -Search a file for lines matching a regular expression pattern. Return the line -number and contents of each matching line. +Search files for lines matching a search string and return all matching lines. -The Unix [`grep`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html) command can be used to search for lines in one or more files -that match a user-provided search query (known as the *pattern*). +The Unix [`grep`][grep] command searches files for lines that match a regular expression. +Your task is to implement a simplified `grep` command, which supports searching for fixed strings. The `grep` command takes three arguments: -1. The pattern used to match lines in a file. -2. Zero or more flags to customize the matching behavior. -3. One or more files in which to search for matching lines. +1. The string to search for. +2. Zero or more flags for customizing the command's behavior. +3. One or more files to search in. -Your task is to implement the `grep` function: given a list of files, find all -lines that match the specified pattern. -Return the lines in the order they appear in the files. -You'll also have to handle options (given as flags), which control how matching -is done and how the results are to be reported. - -As an example, suppose there is a file named "input.txt" with the following contents: - -```text -hello -world -hello again -``` - -If we were to call `grep "hello" input.txt`, the result should be: - -```text -hello -hello again -``` - -If given multiple files, `grep` should prefix each found line with the file it was found in. -As an example: - -```text -input.txt:hello -input.txt:hello again -greeting.txt:hello world -``` - -If given just one file, this prefix is not present. +It then reads the contents of the specified files (in the order specified), finds the lines that contain the search string, and finally returns those lines in the order in which they were found. +When searching in multiple files, each matching line is prepended by the file name and a colon (':'). ## Flags -As said earlier, the `grep` command should also support the following flags: - -- `-n` Prefix each matching line with its line number within its file. - When multiple files are present, this prefix goes *after* the filename prefix. -- `-l` Print only the names of files that contain at least one matching line. -- `-i` Match line using a case-insensitive comparison. -- `-v` Invert the program -- collect all lines that fail to match the pattern. -- `-x` Only match entire lines, instead of lines that contain a match. - -If we run `grep -n "hello" input.txt`, the `-n` flag will require the matching -lines to be prefixed with its line number: - -```text -1:hello -3:hello again -``` - -And if we run `grep -i "HELLO" input.txt`, we'll do a case-insensitive match, -and the output will be: - -```text -hello -hello again -``` +The `grep` command supports the following flags: -The `grep` command should support multiple flags at once. +- `-n` Prepend the line number and a colon (':') to each line in the output, placing the number after the filename (if present). +- `-l` Output only the names of the files that contain at least one matching line. +- `-i` Match using a case-insensitive comparison. +- `-v` Invert the program -- collect all lines that fail to match. +- `-x` Search only for lines where the search string matches the entire line. -For example, running `grep -l -v "hello" file1.txt file2.txt` should -print the names of files that do not contain the string "hello". +[grep]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index 12381074f5..020fdd02d4 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -2,11 +2,17 @@ Calculate the Hamming Distance between two DNA strands. -Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! +Your body is made up of cells that contain DNA. +Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. +In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! -When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. This is known as the "Hamming Distance". +When cells divide, their DNA replicates too. +Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. +If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. +This is known as the "Hamming Distance". -We read DNA using the letters C,A,G and T. Two strands might look like this: +We read DNA using the letters C,A,G and T. +Two strands might look like this: GAGCCTACTAACGGGAT CATCGTAATGACGGCCT @@ -18,6 +24,4 @@ The Hamming Distance is useful for lots of things in science, not just biology, ## Implementation notes -The Hamming distance is only defined for sequences of equal length, so -an attempt to calculate it between sequences of different lengths should -not work. +The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work. diff --git a/exercises/practice/hangman/.docs/instructions.md b/exercises/practice/hangman/.docs/instructions.md index 2b0f335670..227e73175c 100644 --- a/exercises/practice/hangman/.docs/instructions.md +++ b/exercises/practice/hangman/.docs/instructions.md @@ -2,17 +2,13 @@ Implement the logic of the hangman game using functional reactive programming. -[Hangman][] is a simple word guessing game. +[Hangman][hangman] is a simple word guessing game. -[Functional Reactive Programming][frp] is a way to write interactive -programs. It differs from the usual perspective in that instead of -saying "when the button is pressed increment the counter", you write -"the value of the counter is the sum of the number of times the button -is pressed." +[Functional Reactive Programming][frp] is a way to write interactive programs. +It differs from the usual perspective in that instead of saying "when the button is pressed increment the counter", you write "the value of the counter is the sum of the number of times the button is pressed." -Implement the basic logic behind hangman using functional reactive -programming. You'll need to install an FRP library for this, this will -be described in the language/track specific files of the exercise. +Implement the basic logic behind hangman using functional reactive programming. +You'll need to install an FRP library for this, this will be described in the language/track specific files of the exercise. -[Hangman]: https://en.wikipedia.org/wiki/Hangman_%28game%29 +[hangman]: https://en.wikipedia.org/wiki/Hangman_%28game%29 [frp]: https://en.wikipedia.org/wiki/Functional_reactive_programming diff --git a/exercises/practice/hello-world/.docs/instructions.md b/exercises/practice/hello-world/.docs/instructions.md index 0342bf0a4c..c9570e48a9 100644 --- a/exercises/practice/hello-world/.docs/instructions.md +++ b/exercises/practice/hello-world/.docs/instructions.md @@ -1,10 +1,9 @@ # Instructions -The classical introductory exercise. Just say "Hello, World!". +The classical introductory exercise. +Just say "Hello, World!". -["Hello, World!"](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program) is -the traditional first program for beginning programming in a new language -or environment. +["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment. The objectives are simple: @@ -13,3 +12,5 @@ The objectives are simple: - Submit your solution and check it at the website. If everything goes well, you will be ready to fetch your first real exercise. + +[hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program diff --git a/exercises/practice/house/.docs/instructions.md b/exercises/practice/house/.docs/instructions.md index 92174617f3..88928c5fa2 100644 --- a/exercises/practice/house/.docs/instructions.md +++ b/exercises/practice/house/.docs/instructions.md @@ -2,14 +2,11 @@ Recite the nursery rhyme 'This is the House that Jack Built'. -> [The] process of placing a phrase of clause within another phrase of -> clause is called embedding. It is through the processes of recursion -> and embedding that we are able to take a finite number of forms (words -> and phrases) and construct an infinite number of expressions. -> Furthermore, embedding also allows us to construct an infinitely long -> structure, in theory anyway. +> [The] process of placing a phrase of clause within another phrase of clause is called embedding. +> It is through the processes of recursion and embedding that we are able to take a finite number of forms (words and phrases) and construct an infinite number of expressions. +> Furthermore, embedding also allows us to construct an infinitely long structure, in theory anyway. -- [papyr.com](http://papyr.com/hypertextbooks/grammar/ph_noun.htm) +- [papyr.com][papyr] The nursery rhyme reads as follows: @@ -104,3 +101,5 @@ that killed the rat that ate the malt that lay in the house that Jack built. ``` + +[papyr]: https://papyr.com/hypertextbooks/grammar/ph_noun.htm diff --git a/exercises/practice/isbn-verifier/.docs/instructions.md b/exercises/practice/isbn-verifier/.docs/instructions.md index ff94a6614e..4a0244e552 100644 --- a/exercises/practice/isbn-verifier/.docs/instructions.md +++ b/exercises/practice/isbn-verifier/.docs/instructions.md @@ -1,11 +1,13 @@ # Instructions -The [ISBN-10 verification process](https://en.wikipedia.org/wiki/International_Standard_Book_Number) is used to validate book identification -numbers. These normally contain dashes and look like: `3-598-21508-8` +The [ISBN-10 verification process][isbn-verification] is used to validate book identification numbers. +These normally contain dashes and look like: `3-598-21508-8` ## ISBN -The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). In the case the check character is an X, this represents the value '10'. These may be communicated with or without hyphens, and can be checked for their validity by the following formula: +The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). +In the case the check character is an X, this represents the value '10'. +These may be communicated with or without hyphens, and can be checked for their validity by the following formula: ```text (d₁ * 10 + d₂ * 9 + d₃ * 8 + d₄ * 7 + d₅ * 6 + d₆ * 5 + d₇ * 4 + d₈ * 3 + d₉ * 2 + d₁₀ * 1) mod 11 == 0 @@ -15,7 +17,8 @@ If the result is 0, then it is a valid ISBN-10, otherwise it is invalid. ## Example -Let's take the ISBN-10 `3-598-21508-8`. We plug it in to the formula, and get: +Let's take the ISBN-10 `3-598-21508-8`. +We plug it in to the formula, and get: ```text (3 * 10 + 5 * 9 + 9 * 8 + 8 * 7 + 2 * 6 + 1 * 5 + 5 * 4 + 0 * 3 + 8 * 2 + 8 * 1) mod 11 == 0 @@ -33,10 +36,7 @@ The program should be able to verify ISBN-10 both with and without separating da ## Caveats Converting from strings to numbers can be tricky in certain languages. -Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). For instance `3-598-21507-X` is a valid ISBN-10. +Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). +For instance `3-598-21507-X` is a valid ISBN-10. -## Bonus tasks - -* Generate a valid ISBN-13 from the input ISBN-10 (and maybe verify it again with a derived verifier). - -* Generate valid ISBN, maybe even from a given starting ISBN. +[isbn-verification]: https://en.wikipedia.org/wiki/International_Standard_Book_Number diff --git a/exercises/practice/kindergarten-garden/.docs/instructions.md b/exercises/practice/kindergarten-garden/.docs/instructions.md index ba89ff9b05..472ee26f6c 100644 --- a/exercises/practice/kindergarten-garden/.docs/instructions.md +++ b/exercises/practice/kindergarten-garden/.docs/instructions.md @@ -3,9 +3,8 @@ Given a diagram, determine which plants each child in the kindergarten class is responsible for. -The kindergarten class is learning about growing plants. The teacher -thought it would be a good idea to give them actual seeds, plant them in -actual dirt, and grow actual plants. +The kindergarten class is learning about growing plants. +The teacher thought it would be a good idea to give them actual seeds, plant them in actual dirt, and grow actual plants. They've chosen to grow grass, clover, radishes, and violets. @@ -25,8 +24,8 @@ There are 12 children in the class: - Eve, Fred, Ginny, Harriet, - Ileana, Joseph, Kincaid, and Larry. -Each child gets 4 cups, two on each row. Their teacher assigns cups to -the children alphabetically by their names. +Each child gets 4 cups, two on each row. +Their teacher assigns cups to the children alphabetically by their names. The following diagram represents Alice's plants: @@ -36,12 +35,11 @@ VR...................... RG...................... ``` -In the first row, nearest the windows, she has a violet and a radish. In the -second row she has a radish and some grass. +In the first row, nearest the windows, she has a violet and a radish. +In the second row she has a radish and some grass. -Your program will be given the plants from left-to-right starting with -the row nearest the windows. From this, it should be able to determine -which plants belong to each student. +Your program will be given the plants from left-to-right starting with the row nearest the windows. +From this, it should be able to determine which plants belong to each student. For example, if it's told that the garden looks like so: diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index 812d1010d1..1dbbca91c2 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -2,21 +2,18 @@ In this exercise, let's try to solve a classic problem. -Bob is a thief. After months of careful planning, he finally manages to -crack the security systems of a high-class apartment. +Bob is a thief. +After months of careful planning, he finally manages to crack the security systems of a high-class apartment. -In front of him are many items, each with a value (v) and weight (w). Bob, -of course, wants to maximize the total value he can get; he would gladly -take all of the items if he could. However, to his horror, he realizes that -the knapsack he carries with him can only hold so much weight (W). +In front of him are many items, each with a value (v) and weight (w). +Bob, of course, wants to maximize the total value he can get; he would gladly take all of the items if he could. +However, to his horror, he realizes that the knapsack he carries with him can only hold so much weight (W). -Given a knapsack with a specific carrying capacity (W), help Bob determine -the maximum value he can get from the items in the house. Note that Bob can -take only one of each item. +Given a knapsack with a specific carrying capacity (W), help Bob determine the maximum value he can get from the items in the house. +Note that Bob can take only one of each item. -All values given will be strictly positive. Items will be represented as a -list of pairs, `wi` and `vi`, where the first element `wi` is the weight of -the *i*th item and `vi` is the value for that item. +All values given will be strictly positive. +Items will be represented as a list of pairs, `wi` and `vi`, where the first element `wi` is the weight of the *i*th item and `vi` is the value for that item. For example: @@ -29,9 +26,7 @@ Items: [ Knapsack Limit: 10 -For the above, the first item has weight 5 and value 10, the second item has -weight 4 and value 40, and so on. +For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. -In this example, Bob should take the second and fourth item to maximize his -value, which, in this case, is 90. He cannot get more than 90 as his -knapsack has a weight limit of 10. +In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. +He cannot get more than 90 as his knapsack has a weight limit of 10. diff --git a/exercises/practice/largest-series-product/.docs/instructions.md b/exercises/practice/largest-series-product/.docs/instructions.md index 5a3c18de5c..08586dd593 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.md +++ b/exercises/practice/largest-series-product/.docs/instructions.md @@ -1,14 +1,10 @@ # Instructions -Given a string of digits, calculate the largest product for a contiguous -substring of digits of length n. +Given a string of digits, calculate the largest product for a contiguous substring of digits of length n. -For example, for the input `'1027839564'`, the largest product for a -series of 3 digits is 270 (9 \* 5 \* 6), and the largest product for a -series of 5 digits is 7560 (7 \* 8 \* 3 \* 9 \* 5). +For example, for the input `'1027839564'`, the largest product for a series of 3 digits is 270 `(9 * 5 * 6)`, and the largest product for a series of 5 digits is 7560 `(7 * 8 * 3 * 9 * 5)`. -Note that these series are only required to occupy *adjacent positions* -in the input; the digits need not be *numerically consecutive*. +Note that these series are only required to occupy *adjacent positions* in the input; the digits need not be *numerically consecutive*. For the input `'73167176531330624919225119674426574742355349194934'`, the largest product for a series of 6 digits is 23520. diff --git a/exercises/practice/leap/.docs/instructions.md b/exercises/practice/leap/.docs/instructions.md index dc7b4e8164..a83826b2e0 100644 --- a/exercises/practice/leap/.docs/instructions.md +++ b/exercises/practice/leap/.docs/instructions.md @@ -10,15 +10,13 @@ on every year that is evenly divisible by 4 unless the year is also evenly divisible by 400 ``` -For example, 1997 is not a leap year, but 1996 is. 1900 is not a leap -year, but 2000 is. +For example, 1997 is not a leap year, but 1996 is. +1900 is not a leap year, but 2000 is. ## Notes -Though our exercise adopts some very simple rules, there is more to -learn! +Though our exercise adopts some very simple rules, there is more to learn! -For a delightful, four minute explanation of the whole leap year -phenomenon, go watch [this youtube video][video]. +For a delightful, four minute explanation of the whole leap year phenomenon, go watch [this youtube video][video]. -[video]: http://www.youtube.com/watch?v=xX96xng7sAE +[video]: https://www.youtube.com/watch?v=xX96xng7sAE diff --git a/exercises/practice/ledger/.docs/instructions.md b/exercises/practice/ledger/.docs/instructions.md index 266054c36e..a53e5c15e3 100644 --- a/exercises/practice/ledger/.docs/instructions.md +++ b/exercises/practice/ledger/.docs/instructions.md @@ -2,14 +2,13 @@ Refactor a ledger printer. -The ledger exercise is a refactoring exercise. There is code that prints a -nicely formatted ledger, given a locale (American or Dutch) and a currency (US -dollar or euro). The code however is rather badly written, though (somewhat -surprisingly) it consistently passes the test suite. +The ledger exercise is a refactoring exercise. +There is code that prints a nicely formatted ledger, given a locale (American or Dutch) and a currency (US dollar or euro). +The code however is rather badly written, though (somewhat surprisingly) it consistently passes the test suite. -Rewrite this code. Remember that in refactoring the trick is to make small steps -that keep the tests passing. That way you can always quickly go back to a -working version. Version control tools like git can help here as well. +Rewrite this code. +Remember that in refactoring the trick is to make small steps that keep the tests passing. +That way you can always quickly go back to a working version. +Version control tools like git can help here as well. -Please keep a log of what changes you've made and make a comment on the exercise -containing that log, this will help reviewers. +Please keep a log of what changes you've made and make a comment on the exercise containing that log, this will help reviewers. diff --git a/exercises/practice/linked-list/.docs/instructions.md b/exercises/practice/linked-list/.docs/instructions.md index d1bd87551b..3d949d3935 100644 --- a/exercises/practice/linked-list/.docs/instructions.md +++ b/exercises/practice/linked-list/.docs/instructions.md @@ -2,27 +2,25 @@ Implement a doubly linked list. -Like an array, a linked list is a simple linear data structure. Several -common data types can be implemented using linked lists, like queues, -stacks, and associative arrays. - -A linked list is a collection of data elements called *nodes*. In a -*singly linked list* each node holds a value and a link to the next node. -In a *doubly linked list* each node also holds a link to the previous -node. - -You will write an implementation of a doubly linked list. Implement a -Node to hold a value and pointers to the next and previous nodes. Then -implement a List which holds references to the first and last node and -offers an array-like interface for adding and removing items: - -* `push` (*insert value at back*); -* `pop` (*remove value at back*); -* `shift` (*remove value at front*). -* `unshift` (*insert value at front*); - -To keep your implementation simple, the tests will not cover error -conditions. Specifically: `pop` or `shift` will never be called on an -empty list. - -If you want to know more about linked lists, check [Wikipedia](https://en.wikipedia.org/wiki/Linked_list). +Like an array, a linked list is a simple linear data structure. +Several common data types can be implemented using linked lists, like queues, stacks, and associative arrays. + +A linked list is a collection of data elements called *nodes*. +In a *singly linked list* each node holds a value and a link to the next node. +In a *doubly linked list* each node also holds a link to the previous node. + +You will write an implementation of a doubly linked list. +Implement a Node to hold a value and pointers to the next and previous nodes. +Then implement a List which holds references to the first and last node and offers an array-like interface for adding and removing items: + +- `push` (*insert value at back*); +- `pop` (*remove value at back*); +- `shift` (*remove value at front*). +- `unshift` (*insert value at front*); + +To keep your implementation simple, the tests will not cover error conditions. +Specifically: `pop` or `shift` will never be called on an empty list. + +Read more about [linked lists on Wikipedia][linked-lists]. + +[linked-lists]: https://en.wikipedia.org/wiki/Linked_list diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md index b5b20ff20a..d34533387a 100644 --- a/exercises/practice/list-ops/.docs/instructions.md +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -2,19 +2,16 @@ Implement basic list operations. -In functional languages list operations like `length`, `map`, and -`reduce` are very common. Implement a series of basic list operations, -without using existing functions. +In functional languages list operations like `length`, `map`, and `reduce` are very common. +Implement a series of basic list operations, without using existing functions. -The precise number and names of the operations to be implemented will be -track dependent to avoid conflicts with existing names, but the general -operations you will implement include: +The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include: -* `append` (*given two lists, add all items in the second list to the end of the first list*); -* `concatenate` (*given a series of lists, combine all items in all lists into one flattened list*); -* `filter` (*given a predicate and a list, return the list of all items for which `predicate(item)` is True*); -* `length` (*given a list, return the total number of items within it*); -* `map` (*given a function and a list, return the list of the results of applying `function(item)` on all items*); -* `foldl` (*given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left using `function(accumulator, item)`*); -* `foldr` (*given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`*); -* `reverse` (*given a list, return a list with all the original items, but in reversed order*); +- `append` (*given two lists, add all items in the second list to the end of the first list*); +- `concatenate` (*given a series of lists, combine all items in all lists into one flattened list*); +- `filter` (*given a predicate and a list, return the list of all items for which `predicate(item)` is True*); +- `length` (*given a list, return the total number of items within it*); +- `map` (*given a function and a list, return the list of the results of applying `function(item)` on all items*); +- `foldl` (*given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left using `function(accumulator, item)`*); +- `foldr` (*given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`*); +- `reverse` (*given a list, return a list with all the original items, but in reversed order*); diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index f1215dd38c..8cbe791fc2 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -2,18 +2,15 @@ Given a number determine whether or not it is valid per the Luhn formula. -The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is -a simple checksum formula used to validate a variety of identification -numbers, such as credit card numbers and Canadian Social Insurance -Numbers. +The [Luhn algorithm][luhn] is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers. The task is to check if a given string is valid. ## Validating a Number -Strings of length 1 or less are not valid. Spaces are allowed in the input, -but they should be stripped before checking. All other non-digit characters -are disallowed. +Strings of length 1 or less are not valid. +Spaces are allowed in the input, but they should be stripped before checking. +All other non-digit characters are disallowed. ### Example 1: valid credit card number @@ -21,15 +18,15 @@ are disallowed. 4539 3195 0343 6467 ``` -The first step of the Luhn algorithm is to double every second digit, -starting from the right. We will be doubling +The first step of the Luhn algorithm is to double every second digit, starting from the right. +We will be doubling ```text 4_3_ 3_9_ 0_4_ 6_6_ ``` -If doubling the number results in a number greater than 9 then subtract 9 -from the product. The results of our doubling: +If doubling the number results in a number greater than 9 then subtract 9 from the product. +The results of our doubling: ```text 8569 6195 0383 3437 @@ -41,7 +38,8 @@ Then sum all of the digits: 8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80 ``` -If the sum is evenly divisible by 10, then the number is valid. This number is valid! +If the sum is evenly divisible by 10, then the number is valid. +This number is valid! ### Example 2: invalid credit card number @@ -62,3 +60,5 @@ Sum the digits ``` 57 is not evenly divisible by 10, so this number is not valid. + +[luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm diff --git a/exercises/practice/markdown/.docs/instructions.md b/exercises/practice/markdown/.docs/instructions.md index 4819b6c2ff..9b756d9917 100644 --- a/exercises/practice/markdown/.docs/instructions.md +++ b/exercises/practice/markdown/.docs/instructions.md @@ -2,14 +2,12 @@ Refactor a Markdown parser. -The markdown exercise is a refactoring exercise. There is code that parses a -given string with [Markdown -syntax](https://guides.github.com/features/mastering-markdown/) and returns the -associated HTML for that string. Even though this code is confusingly written -and hard to follow, somehow it works and all the tests are passing! Your -challenge is to re-write this code to make it easier to read and maintain -while still making sure that all the tests keep passing. - -It would be helpful if you made notes of what you did in your refactoring in -comments so reviewers can see that, but it isn't strictly necessary. The most -important thing is to make the code better! +The markdown exercise is a refactoring exercise. +There is code that parses a given string with [Markdown syntax][markdown] and returns the associated HTML for that string. +Even though this code is confusingly written and hard to follow, somehow it works and all the tests are passing! +Your challenge is to re-write this code to make it easier to read and maintain while still making sure that all the tests keep passing. + +It would be helpful if you made notes of what you did in your refactoring in comments so reviewers can see that, but it isn't strictly necessary. +The most important thing is to make the code better! + +[markdown]: https://guides.github.com/features/mastering-markdown/ diff --git a/exercises/practice/matching-brackets/.docs/instructions.md b/exercises/practice/matching-brackets/.docs/instructions.md index 364ecad213..ca7c8d838e 100644 --- a/exercises/practice/matching-brackets/.docs/instructions.md +++ b/exercises/practice/matching-brackets/.docs/instructions.md @@ -1,5 +1,3 @@ # Instructions -Given a string containing brackets `[]`, braces `{}`, parentheses `()`, -or any combination thereof, verify that any and all pairs are matched -and nested correctly. +Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. diff --git a/exercises/practice/matrix/.docs/instructions.md b/exercises/practice/matrix/.docs/instructions.md index 1b2d0f84b0..dadea8acb5 100644 --- a/exercises/practice/matrix/.docs/instructions.md +++ b/exercises/practice/matrix/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given a string representing a matrix of numbers, return the rows and columns of -that matrix. +Given a string representing a matrix of numbers, return the rows and columns of that matrix. So given a string with embedded newlines like: @@ -23,10 +22,8 @@ representing this matrix: your code should be able to spit out: -- A list of the rows, reading each row left-to-right while moving - top-to-bottom across the rows, -- A list of the columns, reading each column top-to-bottom while moving - from left-to-right. +- A list of the rows, reading each row left-to-right while moving top-to-bottom across the rows, +- A list of the columns, reading each column top-to-bottom while moving from left-to-right. The rows for our example matrix: diff --git a/exercises/practice/meetup/.docs/instructions.md b/exercises/practice/meetup/.docs/instructions.md index bff409f8aa..0694ef583c 100644 --- a/exercises/practice/meetup/.docs/instructions.md +++ b/exercises/practice/meetup/.docs/instructions.md @@ -1,19 +1,51 @@ # Instructions -In this exercise, you will be given a general description of a meetup date and then asked to find the actual meetup date. +Recurring monthly meetups are generally scheduled on the given weekday of a given week each month. +In this exercise you will be given the recurring schedule, along with a month and year, and then asked to find the exact date of the meetup. -Examples of general descriptions are: +For example a meetup might be scheduled on the _first Monday_ of every month. +You might then be asked to find the date that this meetup will happen in January 2018. +In other words, you need to determine the date of the first Monday of January 2018. -- First Monday of January 2022 -- Third Tuesday of August 2021 -- Teenth Wednesday of May 2022 -- Teenth Sunday of July 2021 -- Last Thursday of November 2021 +Similarly, you might be asked to find: -The descriptors you are expected to process are: `first`, `second`, `third`, `fourth`, `fifth`, `last`, `teenth`. +- the third Tuesday of August 2019 (August 20, 2019) +- the teenth Wednesday of May 2020 (May 13, 2020) +- the fourth Sunday of July 2021 (July 25, 2021) +- the last Thursday of November 2022 (November 24, 2022) + +The descriptors you are expected to process are: `first`, `second`, `third`, `fourth`, `last`, `teenth`. Note that descriptor `teenth` is a made-up word. -There are exactly seven numbered days in a month that end with "teenth" ("thirteenth" to "nineteenth"). -Therefore, it is guaranteed that each day of the week (Monday, Tuesday, ...) will have exactly one numbered day ending with "teenth" each month. -For example, if given "First Monday of January 2022", the correct meetup date is January 3, 2022. +It refers to the seven numbers that end in '-teen' in English: 13, 14, 15, 16, 17, 18, and 19. +But general descriptions of dates use ordinal numbers, e.g. the _first_ Monday, the _third_ Tuesday. + +For the numbers ending in '-teen', that becomes: + +- 13th (thirteenth) +- 14th (fourteenth) +- 15th (fifteenth) +- 16th (sixteenth) +- 17th (seventeenth) +- 18th (eighteenth) +- 19th (nineteenth) + +So there are seven numbers ending in '-teen'. +And there are also seven weekdays (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday). +Therefore, it is guaranteed that each day of the week (Monday, Tuesday, ...) will have exactly one numbered day ending with "teen" each month. + +If asked to find the teenth Saturday of August, 1953 (or, alternately the "Saturteenth" of August, 1953), we need to look at the calendar for August 1953: + +```plaintext + August 1953 +Su Mo Tu We Th Fr Sa + 1 + 2 3 4 5 6 7 8 + 9 10 11 12 13 14 15 +16 17 18 19 20 21 22 +23 24 25 26 27 28 29 +30 31 +``` + +The Saturday that has a number ending in '-teen' is August 15, 1953. diff --git a/exercises/practice/minesweeper/.docs/instructions.md b/exercises/practice/minesweeper/.docs/instructions.md index d1f99c9a9c..f5f918bdff 100644 --- a/exercises/practice/minesweeper/.docs/instructions.md +++ b/exercises/practice/minesweeper/.docs/instructions.md @@ -2,23 +2,18 @@ Add the mine counts to a completed Minesweeper board. -Minesweeper is a popular game where the user has to find the mines using -numeric hints that indicate how many mines are directly adjacent -(horizontally, vertically, diagonally) to a square. +Minesweeper is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. -In this exercise you have to create some code that counts the number of -mines adjacent to a given empty square and replaces that square with the -count. +In this exercise you have to create some code that counts the number of mines adjacent to a given empty square and replaces that square with the count. -The board is a rectangle composed of blank space (' ') characters. A mine -is represented by an asterisk ('\*') character. +The board is a rectangle composed of blank space (' ') characters. +A mine is represented by an asterisk (`*`) character. If a given space has no adjacent mines at all, leave that square blank. ## Examples -For example you may receive a 5 x 4 board like this (empty spaces are -represented here with the '·' character for display on screen): +For example you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): ```text ·*·*· diff --git a/exercises/practice/nth-prime/.docs/instructions.md b/exercises/practice/nth-prime/.docs/instructions.md index 30a75216fd..065e323ab2 100644 --- a/exercises/practice/nth-prime/.docs/instructions.md +++ b/exercises/practice/nth-prime/.docs/instructions.md @@ -2,8 +2,6 @@ Given a number n, determine what the nth prime is. -By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that -the 6th prime is 13. +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. -If your language provides methods in the standard library to deal with prime -numbers, pretend they don't exist and implement them yourself. +If your language provides methods in the standard library to deal with prime numbers, pretend they don't exist and implement them yourself. diff --git a/exercises/practice/ocr-numbers/.docs/instructions.md b/exercises/practice/ocr-numbers/.docs/instructions.md index c3a3c514bf..7beb257795 100644 --- a/exercises/practice/ocr-numbers/.docs/instructions.md +++ b/exercises/practice/ocr-numbers/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is -represented, or whether it is garbled. +Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled. ## Step One @@ -59,7 +58,8 @@ Is converted to "1234567890" ## Step Four -Update your program to handle multiple numbers, one per line. When converting several lines, join the lines with commas. +Update your program to handle multiple numbers, one per line. +When converting several lines, join the lines with commas. ```text _ _ @@ -76,4 +76,4 @@ Update your program to handle multiple numbers, one per line. When converting se ``` -Is converted to "123,456,789" +Is converted to "123,456,789". diff --git a/exercises/practice/paasio/.docs/instructions.md b/exercises/practice/paasio/.docs/instructions.md index 67aaefaaa7..5956447487 100644 --- a/exercises/practice/paasio/.docs/instructions.md +++ b/exercises/practice/paasio/.docs/instructions.md @@ -2,13 +2,12 @@ Report network IO statistics. -You are writing a [PaaS][], and you need a way to bill customers based -on network and filesystem usage. +You are writing a [PaaS][paas], and you need a way to bill customers based on network and filesystem usage. -Create a wrapper for network connections and files that can report IO -statistics. The wrapper must report: +Create a wrapper for network connections and files that can report IO statistics. +The wrapper must report: - The total number of bytes read/written. - The total number of read/write operations. -[PaaS]: http://en.wikipedia.org/wiki/Platform_as_a_service +[paas]: https://en.wikipedia.org/wiki/Platform_as_a_service diff --git a/exercises/practice/palindrome-products/.docs/instructions.md b/exercises/practice/palindrome-products/.docs/instructions.md index fd9a441247..aac66521ce 100644 --- a/exercises/practice/palindrome-products/.docs/instructions.md +++ b/exercises/practice/palindrome-products/.docs/instructions.md @@ -2,15 +2,14 @@ Detect palindrome products in a given range. -A palindromic number is a number that remains the same when its digits are -reversed. For example, `121` is a palindromic number but `112` is not. +A palindromic number is a number that remains the same when its digits are reversed. +For example, `121` is a palindromic number but `112` is not. Given a range of numbers, find the largest and smallest palindromes which are products of two numbers within that range. -Your solution should return the largest and smallest palindromes, along with the -factors of each within the range. If the largest or smallest palindrome has more -than one pair of factors within the range, then return all the pairs. +Your solution should return the largest and smallest palindromes, along with the factors of each within the range. +If the largest or smallest palindrome has more than one pair of factors within the range, then return all the pairs. ## Example 1 @@ -22,12 +21,16 @@ And given the list of all possible products within this range: The palindrome products are all single digit numbers (in this case): `[1, 2, 3, 4, 5, 6, 7, 8, 9]` -The smallest palindrome product is `1`. Its factors are `(1, 1)`. -The largest palindrome product is `9`. Its factors are `(1, 9)` and `(3, 3)`. +The smallest palindrome product is `1`. +Its factors are `(1, 1)`. +The largest palindrome product is `9`. +Its factors are `(1, 9)` and `(3, 3)`. ## Example 2 Given the range `[10, 99]` (both inclusive)... -The smallest palindrome product is `121`. Its factors are `(11, 11)`. -The largest palindrome product is `9009`. Its factors are `(91, 99)`. +The smallest palindrome product is `121`. +Its factors are `(11, 11)`. +The largest palindrome product is `9009`. +Its factors are `(91, 99)`. diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index 3957ae542e..de83d54eb6 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -1,9 +1,9 @@ # Instructions -Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma, -"every letter") is a sentence using every letter of the alphabet at least once. +Determine if a sentence is a pangram. +A pangram (Greek: παν γράμμα, pan gramma, "every letter") is a sentence using every letter of the alphabet at least once. The best known English pangram is: + > The quick brown fox jumps over the lazy dog. -The alphabet used consists of letters `a` to `z`, inclusive, and is case -insensitive. +The alphabet used consists of letters `a` to `z`, inclusive, and is case insensitive. diff --git a/exercises/practice/perfect-numbers/.docs/instructions.md b/exercises/practice/perfect-numbers/.docs/instructions.md index 144c9133e4..0dae8867ff 100644 --- a/exercises/practice/perfect-numbers/.docs/instructions.md +++ b/exercises/practice/perfect-numbers/.docs/instructions.md @@ -3,7 +3,9 @@ Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. -The Greek mathematician [Nicomachus](https://en.wikipedia.org/wiki/Nicomachus) devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum](https://en.wikipedia.org/wiki/Aliquot_sum). The aliquot sum is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of 15 is (1 + 3 + 5) = 9 +The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum][aliquot-sum]. +The aliquot sum is defined as the sum of the factors of a number not including the number itself. +For example, the aliquot sum of 15 is (1 + 3 + 5) = 9 - **Perfect**: aliquot sum = number - 6 is a perfect number because (1 + 2 + 3) = 6 @@ -15,4 +17,8 @@ The Greek mathematician [Nicomachus](https://en.wikipedia.org/wiki/Nicomachus) d - 8 is a deficient number because (1 + 2 + 4) = 7 - Prime numbers are deficient -Implement a way to determine whether a given number is **perfect**. Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. +Implement a way to determine whether a given number is **perfect**. +Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. + +[nicomachus]: https://en.wikipedia.org/wiki/Nicomachus +[aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 9da4a51960..6d3275cdf2 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -2,9 +2,11 @@ Clean up user-entered phone numbers so that they can be sent SMS messages. -The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. +The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. +All NANP-countries share the same international country code: `1`. -NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as *area code*, followed by a seven-digit local number. The first three digits of the local number represent the *exchange code*, followed by the unique four-digit number which is the *subscriber number*. +NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as *area code*, followed by a seven-digit local number. +The first three digits of the local number represent the *exchange code*, followed by the unique four-digit number which is the *subscriber number*. The format is usually represented as diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index bcb1251176..c9de5ca186 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -2,17 +2,18 @@ Implement a program that translates from English to Pig Latin. -Pig Latin is a made-up children's language that's intended to be -confusing. It obeys a few simple rules (below), but when it's spoken -quickly it's really difficult for non-children (and non-native speakers) -to understand. +Pig Latin is a made-up children's language that's intended to be confusing. +It obeys a few simple rules (below), but when it's spoken quickly it's really difficult for non-children (and non-native speakers) to understand. -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. Consonant sounds can be made up of multiple consonants, a.k.a. a consonant cluster (e.g. "chair" -> "airchay"). +- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. + Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). +- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. + Consonant sounds can be made up of multiple consonants, a.k.a. a consonant cluster (e.g. "chair" -> "airchay"). - **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). - **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). -There are a few more rules for edge cases, and there are regional -variants too. +There are a few more rules for edge cases, and there are regional variants too. -See for more details. +Read more about [Pig Latin on Wikipedia][pig-latin]. + +[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin diff --git a/exercises/practice/poker/.docs/instructions.md b/exercises/practice/poker/.docs/instructions.md index 6a38cf4bc7..492fc4c9e0 100644 --- a/exercises/practice/poker/.docs/instructions.md +++ b/exercises/practice/poker/.docs/instructions.md @@ -2,5 +2,6 @@ Pick the best hand(s) from a list of poker hands. -See [wikipedia](https://en.wikipedia.org/wiki/List_of_poker_hands) for an -overview of poker hands. +See [wikipedia][poker-hands] for an overview of poker hands. + +[poker-hands]: https://en.wikipedia.org/wiki/List_of_poker_hands diff --git a/exercises/practice/pov/.docs/instructions.md b/exercises/practice/pov/.docs/instructions.md index 8c89be730a..0fdeed2250 100644 --- a/exercises/practice/pov/.docs/instructions.md +++ b/exercises/practice/pov/.docs/instructions.md @@ -2,13 +2,11 @@ Reparent a tree on a selected node. -A [tree][wiki-tree] is a special type of [graph][wiki-graph] where all nodes -are connected but there are no cycles. That means, there is exactly one path to -get from one node to another for any pair of nodes. +A [tree][wiki-tree] is a special type of [graph][wiki-graph] where all nodes are connected but there are no cycles. +That means, there is exactly one path to get from one node to another for any pair of nodes. -This exercise is all about re-orientating a tree to see things from a different -point of view. For example family trees are usually presented from the -ancestor's perspective: +This exercise is all about re-orientating a tree to see things from a different point of view. +For example family trees are usually presented from the ancestor's perspective: ```text +------0------+ @@ -18,10 +16,9 @@ ancestor's perspective: 4 5 6 7 8 9 ``` -But there is no inherent direction in a tree. The same information can be -presented from the perspective of any other node in the tree, by pulling it up -to the root and dragging its relationships along with it. So the same tree -from 6's perspective would look like: +But there is no inherent direction in a tree. +The same information can be presented from the perspective of any other node in the tree, by pulling it up to the root and dragging its relationships along with it. +So the same tree from 6's perspective would look like: ```text 6 @@ -35,12 +32,10 @@ from 6's perspective would look like: 4 5 8 9 ``` -This lets us more simply describe the paths between two nodes. So for example -the path from 6-9 (which in the first tree goes up to the root and then down to -a different leaf node) can be seen to follow the path 6-2-0-3-9. +This lets us more simply describe the paths between two nodes. +So for example the path from 6-9 (which in the first tree goes up to the root and then down to a different leaf node) can be seen to follow the path 6-2-0-3-9. -This exercise involves taking an input tree and re-orientating it from the point -of view of one of the nodes. +This exercise involves taking an input tree and re-orientating it from the point of view of one of the nodes. [wiki-graph]: https://en.wikipedia.org/wiki/Tree_(graph_theory) [wiki-tree]: https://en.wikipedia.org/wiki/Graph_(discrete_mathematics) diff --git a/exercises/practice/prime-factors/.docs/instructions.md b/exercises/practice/prime-factors/.docs/instructions.md index 494d3dfc5d..252cc8ee18 100644 --- a/exercises/practice/prime-factors/.docs/instructions.md +++ b/exercises/practice/prime-factors/.docs/instructions.md @@ -10,21 +10,27 @@ Note that 1 is not a prime number. What are the prime factors of 60? -- Our first divisor is 2. 2 goes into 60, leaving 30. +- Our first divisor is 2. + 2 goes into 60, leaving 30. - 2 goes into 30, leaving 15. - - 2 doesn't go cleanly into 15. So let's move on to our next divisor, 3. + - 2 doesn't go cleanly into 15. + So let's move on to our next divisor, 3. - 3 goes cleanly into 15, leaving 5. - - 3 does not go cleanly into 5. The next possible factor is 4. - - 4 does not go cleanly into 5. The next possible factor is 5. + - 3 does not go cleanly into 5. + The next possible factor is 4. + - 4 does not go cleanly into 5. + The next possible factor is 5. - 5 does go cleanly into 5. - We're left only with 1, so now, we're done. -Our successful divisors in that computation represent the list of prime -factors of 60: 2, 2, 3, and 5. +Our successful divisors in that computation represent the list of prime factors of 60: 2, 2, 3, and 5. You can check this yourself: -- 2 \* 2 \* 3 * 5 -- = 4 * 15 -- = 60 -- Success! +```text +2 * 2 * 3 * 5 += 4 * 15 += 60 +``` + +Success! diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index c211345ed9..d9b9054cf5 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -11,7 +11,8 @@ Codons: `"AUG", "UUU", "UCU"` Protein: `"Methionine", "Phenylalanine", "Serine"` -There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. If it works for one codon, the program should work for all of them. +There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. +If it works for one codon, the program should work for all of them. However, feel free to expand the list in the test suite to include them all. There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated. @@ -39,4 +40,6 @@ UGU, UGC | Cysteine UGG | Tryptophan UAA, UAG, UGA | STOP -Learn more about [protein translation on Wikipedia](http://en.wikipedia.org/wiki/Translation_(biology)) +Learn more about [protein translation on Wikipedia][protein-translation]. + +[protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.md b/exercises/practice/pythagorean-triplet/.docs/instructions.md index d74ee4c179..1c1a8aea61 100644 --- a/exercises/practice/pythagorean-triplet/.docs/instructions.md +++ b/exercises/practice/pythagorean-triplet/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for -which, +A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which, ```text a² + b² = c² @@ -16,7 +15,7 @@ a < b < c For example, ```text -3² + 4² = 9 + 16 = 25 = 5². +3² + 4² = 5². ``` Given an input integer N, find all Pythagorean triplets for which `a + b + c = N`. diff --git a/exercises/practice/rail-fence-cipher/.docs/instructions.md b/exercises/practice/rail-fence-cipher/.docs/instructions.md index 0e75a2bf70..e311de6cdf 100644 --- a/exercises/practice/rail-fence-cipher/.docs/instructions.md +++ b/exercises/practice/rail-fence-cipher/.docs/instructions.md @@ -2,15 +2,13 @@ Implement encoding and decoding for the rail fence cipher. -The Rail Fence cipher is a form of transposition cipher that gets its name from -the way in which it's encoded. It was already used by the ancient Greeks. +The Rail Fence cipher is a form of transposition cipher that gets its name from the way in which it's encoded. +It was already used by the ancient Greeks. -In the Rail Fence cipher, the message is written downwards on successive "rails" -of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). +In the Rail Fence cipher, the message is written downwards on successive "rails" of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). Finally the message is then read off in rows. -For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", -the cipherer writes out: +For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", the cipherer writes out: ```text W . . . E . . . C . . . R . . . L . . . T . . . E diff --git a/exercises/practice/raindrops/.docs/instructions.md b/exercises/practice/raindrops/.docs/instructions.md index bf09afa33b..fc61d36e99 100644 --- a/exercises/practice/raindrops/.docs/instructions.md +++ b/exercises/practice/raindrops/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. A factor is a number that evenly divides into another number, leaving no remainder. The simplest way to test if one number is a factor of another is to use the [modulo operation](https://en.wikipedia.org/wiki/Modulo_operation). +Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. +A factor is a number that evenly divides into another number, leaving no remainder. +The simplest way to test if one number is a factor of another is to use the [modulo operation][modulo]. The rules of `raindrops` are that if a given number: @@ -14,3 +16,5 @@ The rules of `raindrops` are that if a given number: - 28 has 7 as a factor, but not 3 or 5, so the result would be "Plong". - 30 has both 3 and 5 as factors, but not 7, so the result would be "PlingPlang". - 34 is not factored by 3, 5, or 7, so the result would be "34". + +[modulo]: https://en.wikipedia.org/wiki/Modulo_operation diff --git a/exercises/practice/rational-numbers/.docs/instructions.md b/exercises/practice/rational-numbers/.docs/instructions.md index c06841e4d1..f64fc0f28e 100644 --- a/exercises/practice/rational-numbers/.docs/instructions.md +++ b/exercises/practice/rational-numbers/.docs/instructions.md @@ -31,7 +31,12 @@ Implement the following operations: - addition, subtraction, multiplication and division of two rational numbers, - absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number. -Your implementation of rational numbers should always be reduced to lowest terms. For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`. -The reduced form of a rational number should be in "standard form" (the denominator should always be a positive integer). If a denominator with a negative integer is present, multiply both numerator and denominator by `-1` to ensure standard form is reached. For example, `3/-4` should be reduced to `-3/4` +Your implementation of rational numbers should always be reduced to lowest terms. +For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. +To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. +So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`. +The reduced form of a rational number should be in "standard form" (the denominator should always be a positive integer). +If a denominator with a negative integer is present, multiply both numerator and denominator by `-1` to ensure standard form is reached. +For example, `3/-4` should be reduced to `-3/4` Assume that the programming language you are using does not have an implementation of rational numbers. diff --git a/exercises/practice/react/.docs/instructions.md b/exercises/practice/react/.docs/instructions.md index 5cad8825f0..1b9a175d0b 100644 --- a/exercises/practice/react/.docs/instructions.md +++ b/exercises/practice/react/.docs/instructions.md @@ -2,15 +2,10 @@ Implement a basic reactive system. -Reactive programming is a programming paradigm that focuses on how values -are computed in terms of each other to allow a change to one value to -automatically propagate to other values, like in a spreadsheet. +Reactive programming is a programming paradigm that focuses on how values are computed in terms of each other to allow a change to one value to automatically propagate to other values, like in a spreadsheet. -Implement a basic reactive system with cells with settable values ("input" -cells) and cells with values computed in terms of other cells ("compute" -cells). Implement updates so that when an input value is changed, values -propagate to reach a new stable system state. +Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells). +Implement updates so that when an input value is changed, values propagate to reach a new stable system state. -In addition, compute cells should allow for registering change notification -callbacks. Call a cell’s callbacks when the cell’s value in a new stable -state has changed from the previous stable state. +In addition, compute cells should allow for registering change notification callbacks. +Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state. diff --git a/exercises/practice/rectangles/.docs/instructions.md b/exercises/practice/rectangles/.docs/instructions.md index 84fc9e5e23..8eb4ed470e 100644 --- a/exercises/practice/rectangles/.docs/instructions.md +++ b/exercises/practice/rectangles/.docs/instructions.md @@ -60,5 +60,4 @@ The above diagram contains these 6 rectangles: ``` -You may assume that the input is always a proper rectangle (i.e. the length of -every line equals the length of the first line). +You may assume that the input is always a proper rectangle (i.e. the length of every line equals the length of the first line). diff --git a/exercises/practice/resistor-color-duo/.docs/instructions.md b/exercises/practice/resistor-color-duo/.docs/instructions.md index 68550f7803..bdcd549b1a 100644 --- a/exercises/practice/resistor-color-duo/.docs/instructions.md +++ b/exercises/practice/resistor-color-duo/.docs/instructions.md @@ -3,8 +3,8 @@ If you want to build something using a Raspberry Pi, you'll probably use _resistors_. For this exercise, you need to know two things about them: -* Each resistor has a resistance value. -* Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. +- Each resistor has a resistance value. +- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. Each band has a position and a numeric value. @@ -17,16 +17,16 @@ The program will take color names as input and output a two digit number, even i The band colors are encoded as follows: -* Black: 0 -* Brown: 1 -* Red: 2 -* Orange: 3 -* Yellow: 4 -* Green: 5 -* Blue: 6 -* Violet: 7 -* Grey: 8 -* White: 9 +- Black: 0 +- Brown: 1 +- Red: 2 +- Orange: 3 +- Yellow: 4 +- Green: 5 +- Blue: 6 +- Violet: 7 +- Grey: 8 +- White: 9 From the example above: brown-green should return 15 diff --git a/exercises/practice/resistor-color/.docs/instructions.md b/exercises/practice/resistor-color/.docs/instructions.md index 41ece3f809..646c14398f 100644 --- a/exercises/practice/resistor-color/.docs/instructions.md +++ b/exercises/practice/resistor-color/.docs/instructions.md @@ -31,6 +31,9 @@ The goal of this exercise is to create a way: - to look up the numerical value associated with a particular color band - to list the different band colors -Mnemonics map the colors to the numbers, that, when stored as an array, happen to map to their index in the array: Better Be Right Or Your Great Big Values Go Wrong. +Mnemonics map the colors to the numbers, that, when stored as an array, happen to map to their index in the array: +Better Be Right Or Your Great Big Values Go Wrong. -More information on the color encoding of resistors can be found in the [Electronic color code Wikipedia article](https://en.wikipedia.org/wiki/Electronic_color_code) +More information on the color encoding of resistors can be found in the [Electronic color code Wikipedia article][e-color-code]. + +[e-color-code]: https://en.wikipedia.org/wiki/Electronic_color_code diff --git a/exercises/practice/rest-api/.docs/instructions.md b/exercises/practice/rest-api/.docs/instructions.md index c4c0a89aae..f3b226a73d 100644 --- a/exercises/practice/rest-api/.docs/instructions.md +++ b/exercises/practice/rest-api/.docs/instructions.md @@ -4,7 +4,7 @@ Implement a RESTful API for tracking IOUs. Four roommates have a habit of borrowing money from each other frequently, and have trouble remembering who owes whom, and how much. -Your task is to implement a simple [RESTful API](https://en.wikipedia.org/wiki/Representational_state_transfer) that receives [IOU](https://en.wikipedia.org/wiki/IOU)s as POST requests, and can deliver specified summary information via GET requests. +Your task is to implement a simple [RESTful API][restful-wikipedia] that receives [IOU][iou]s as POST requests, and can deliver specified summary information via GET requests. ## API Specification @@ -36,7 +36,13 @@ Your task is to implement a simple [RESTful API](https://en.wikipedia.org/wiki/R ## Other Resources -- [https://restfulapi.net/](https://restfulapi.net/) +- [REST API Tutorial][restfulapi] - Example RESTful APIs - - [GitHub](https://developer.github.com/v3/) - - [Reddit](https://www.reddit.com/dev/api/) + - [GitHub][github-rest] + - [Reddit][reddit-rest] + +[restful-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer +[iou]: https://en.wikipedia.org/wiki/IOU +[github-rest]: https://developer.github.com/v3/ +[reddit-rest]: https://www.reddit.com/dev/api/ +[restfulapi]: https://restfulapi.net/ diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index 9e86efea9e..851bdb49db 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -4,16 +4,13 @@ Given a DNA strand, return its RNA complement (per RNA transcription). Both DNA and RNA strands are a sequence of nucleotides. -The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), -guanine (**G**) and thymine (**T**). +The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**) and thymine (**T**). -The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), -guanine (**G**) and uracil (**U**). +The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**) and uracil (**U**). -Given a DNA strand, its transcribed RNA strand is formed by replacing -each nucleotide with its complement: +Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement: -* `G` -> `C` -* `C` -> `G` -* `T` -> `A` -* `A` -> `U` +- `G` -> `C` +- `C` -> `G` +- `T` -> `A` +- `A` -> `U` diff --git a/exercises/practice/robot-name/.docs/instructions.md b/exercises/practice/robot-name/.docs/instructions.md index a0079a341e..fca3a41aec 100644 --- a/exercises/practice/robot-name/.docs/instructions.md +++ b/exercises/practice/robot-name/.docs/instructions.md @@ -4,13 +4,11 @@ Manage robot factory settings. When a robot comes off the factory floor, it has no name. -The first time you turn on a robot, a random name is generated in the format -of two uppercase letters followed by three digits, such as RX837 or BC811. +The first time you turn on a robot, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811. -Every once in a while we need to reset a robot to its factory settings, -which means that its name gets wiped. The next time you ask, that robot will -respond with a new random name. +Every once in a while we need to reset a robot to its factory settings, which means that its name gets wiped. +The next time you ask, that robot will respond with a new random name. The names must be random: they should not follow a predictable sequence. -Using random names means a risk of collisions. Your solution must ensure that -every existing robot has a unique name. +Using random names means a risk of collisions. +Your solution must ensure that every existing robot has a unique name. diff --git a/exercises/practice/robot-simulator/.docs/instructions.md b/exercises/practice/robot-simulator/.docs/instructions.md index 83be50ccc5..0ac96ce0bd 100644 --- a/exercises/practice/robot-simulator/.docs/instructions.md +++ b/exercises/practice/robot-simulator/.docs/instructions.md @@ -10,13 +10,10 @@ The robots have three possible movements: - turn left - advance -Robots are placed on a hypothetical infinite grid, facing a particular -direction (north, east, south, or west) at a set of {x,y} coordinates, +Robots are placed on a hypothetical infinite grid, facing a particular direction (north, east, south, or west) at a set of {x,y} coordinates, e.g., {3,8}, with coordinates increasing to the north and east. -The robot then receives a number of instructions, at which point the -testing facility verifies the robot's new position, and in which -direction it is pointing. +The robot then receives a number of instructions, at which point the testing facility verifies the robot's new position, and in which direction it is pointing. - The letter-string "RAALAL" means: - Turn right @@ -24,5 +21,5 @@ direction it is pointing. - Turn left - Advance once - Turn left yet again -- Say a robot starts at {7, 3} facing north. Then running this stream - of instructions should leave it at {9, 4} facing west. +- Say a robot starts at {7, 3} facing north. + Then running this stream of instructions should leave it at {9, 4} facing west. diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md index 621565cf64..bb7e909dbf 100644 --- a/exercises/practice/roman-numerals/.docs/instructions.md +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -2,17 +2,15 @@ Write a function to convert from normal numbers to Roman Numerals. -The Romans were a clever bunch. They conquered most of Europe and ruled -it for hundreds of years. They invented concrete and straight roads and -even bikinis. One thing they never discovered though was the number -zero. This made writing and dating extensive histories of their exploits -slightly more challenging, but the system of numbers they came up with -is still in use today. For example the BBC uses Roman numerals to date -their programs. - -The Romans wrote numbers using letters - I, V, X, L, C, D, M. (notice -these letters have lots of straight lines and are hence easy to hack -into stone tablets). +The Romans were a clever bunch. +They conquered most of Europe and ruled it for hundreds of years. +They invented concrete and straight roads and even bikinis. +One thing they never discovered though was the number zero. +This made writing and dating extensive histories of their exploits slightly more challenging, but the system of numbers they came up with is still in use today. +For example the BBC uses Roman numerals to date their programs. + +The Romans wrote numbers using letters - I, V, X, L, C, D, M. +(notice these letters have lots of straight lines and are hence easy to hack into stone tablets). ```text 1 => I @@ -20,12 +18,10 @@ into stone tablets). 7 => VII ``` -There is no need to be able to convert numbers larger than about 3000. +The maximum number supported by this notation is 3,999. (The Romans themselves didn't tend to go any higher) -Wikipedia says: Modern Roman numerals ... are written by expressing each -digit separately starting with the left most digit and skipping any -digit with a value of zero. +Wikipedia says: Modern Roman numerals ... are written by expressing each digit separately starting with the left most digit and skipping any digit with a value of zero. To see this in practice, consider the example of 1990. @@ -40,4 +36,6 @@ In Roman numerals 1990 is MCMXC: 2000=MM 8=VIII -See also: [http://www.novaroma.org/via_romana/numbers.html](http://www.novaroma.org/via_romana/numbers.html) +Learn more about [Roman numberals on Wikipedia][roman-numerals]. + +[roman-numerals]: https://wiki.imperivm-romanvm.com/wiki/Roman_Numerals diff --git a/exercises/practice/rotational-cipher/.docs/instructions.md b/exercises/practice/rotational-cipher/.docs/instructions.md index dbf6276f37..4dee51b355 100644 --- a/exercises/practice/rotational-cipher/.docs/instructions.md +++ b/exercises/practice/rotational-cipher/.docs/instructions.md @@ -2,11 +2,9 @@ Create an implementation of the rotational cipher, also sometimes called the Caesar cipher. -The Caesar cipher is a simple shift cipher that relies on -transposing all the letters in the alphabet using an integer key -between `0` and `26`. Using a key of `0` or `26` will always yield -the same output due to modular arithmetic. The letter is shifted -for as many values as the value of the key. +The Caesar cipher is a simple shift cipher that relies on transposing all the letters in the alphabet using an integer key between `0` and `26`. +Using a key of `0` or `26` will always yield the same output due to modular arithmetic. +The letter is shifted for as many values as the value of the key. The general notation for rotational ciphers is `ROT + `. The most commonly used rotational cipher is `ROT13`. diff --git a/exercises/practice/run-length-encoding/.docs/instructions.md b/exercises/practice/run-length-encoding/.docs/instructions.md index 95f7a9d69c..fc8ce05694 100644 --- a/exercises/practice/run-length-encoding/.docs/instructions.md +++ b/exercises/practice/run-length-encoding/.docs/instructions.md @@ -2,8 +2,7 @@ Implement run-length encoding and decoding. -Run-length encoding (RLE) is a simple form of data compression, where runs -(consecutive data elements) are replaced by just one data value and count. +Run-length encoding (RLE) is a simple form of data compression, where runs (consecutive data elements) are replaced by just one data value and count. For example we can represent the original 53 characters with only 13. @@ -11,14 +10,11 @@ For example we can represent the original 53 characters with only 13. "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" -> "12WB12W3B24WB" ``` -RLE allows the original data to be perfectly reconstructed from -the compressed data, which makes it a lossless data compression. +RLE allows the original data to be perfectly reconstructed from the compressed data, which makes it a lossless data compression. ```text "AABCCCDEEEE" -> "2AB3CD4E" -> "AABCCCDEEEE" ``` -For simplicity, you can assume that the unencoded string will only contain -the letters A through Z (either lower or upper case) and whitespace. This way -data to be encoded will never contain any numbers and numbers inside data to -be decoded always represent the count for the following character. +For simplicity, you can assume that the unencoded string will only contain the letters A through Z (either lower or upper case) and whitespace. +This way data to be encoded will never contain any numbers and numbers inside data to be decoded always represent the count for the following character. diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index 3a22509d94..920ecffed9 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -14,16 +14,12 @@ So say you have a matrix like so: It has a saddle point at row 2, column 1. -It's called a "saddle point" because it is greater than or equal to -every element in its row and less than or equal to every element in -its column. +It's called a "saddle point" because it is greater than or equal to every element in its row and less than or equal to every element in its column. A matrix may have zero or more saddle points. -Your code should be able to provide the (possibly empty) list of all the -saddle points for any given matrix. +Your code should be able to provide the (possibly empty) list of all the saddle points for any given matrix. The matrix can have a different number of rows and columns (Non square). -Note that you may find other definitions of matrix saddle points online, -but the tests for this exercise follow the above unambiguous definition. +Note that you may find other definitions of matrix saddle points online, but the tests for this exercise follow the above unambiguous definition. diff --git a/exercises/practice/satellite/.docs/instructions.md b/exercises/practice/satellite/.docs/instructions.md index 43ebe63ffe..fbbf14f439 100644 --- a/exercises/practice/satellite/.docs/instructions.md +++ b/exercises/practice/satellite/.docs/instructions.md @@ -1,17 +1,15 @@ # Instructions -Imagine you need to transmit a binary tree to a satellite approaching Alpha -Centauri and you have limited bandwidth. Since the tree has no repeating -items it can be uniquely represented by its [pre-order and in-order traversals][wiki]. +Imagine you need to transmit a binary tree to a satellite approaching Alpha Centauri and you have limited bandwidth. +Since the tree has no repeating items it can be uniquely represented by its [pre-order and in-order traversals][wiki]. Write the software for the satellite to rebuild the tree from the traversals. -A pre-order traversal reads the value of the current node before (hence "pre") -reading the left subtree in pre-order. Afterwards the right subtree is read -in pre-order. +A pre-order traversal reads the value of the current node before (hence "pre") reading the left subtree in pre-order. +Afterwards the right subtree is read in pre-order. -An in-order traversal reads the left subtree in-order then the current node and -finally the right subtree in-order. So in order from left to right. +An in-order traversal reads the left subtree in-order then the current node and finally the right subtree in-order. +So in order from left to right. For example the pre-order traversal of this tree is [a, i, x, f, r]. The in-order traversal of this tree is [i, a, f, x, r] diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index 727b0186db..aa2e7687fe 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -6,11 +6,9 @@ Given a number from 0 to 999,999,999,999, spell out that number in English. Handle the basic case of 0 through 99. -If the input to the program is `22`, then the output should be -`'twenty-two'`. +If the input to the program is `22`, then the output should be `'twenty-two'`. -Your program should complain loudly if given a number outside the -blessed range. +Your program should complain loudly if given a number outside the blessed range. Some good test cases for this program are: @@ -23,15 +21,14 @@ Some good test cases for this program are: ### Extension -If you're on a Mac, shell out to Mac OS X's `say` program to talk out -loud. If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`. +If you're on a Mac, shell out to Mac OS X's `say` program to talk out loud. +If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`. ## Step 2 Implement breaking a number up into chunks of thousands. -So `1234567890` should yield a list like 1, 234, 567, and 890, while the -far simpler `1000` should yield just 1 and 0. +So `1234567890` should yield a list like 1, 234, 567, and 890, while the far simpler `1000` should yield just 1 and 0. The program must also report any values that are out of range. @@ -41,8 +38,8 @@ Now handle inserting the appropriate scale word between those chunks. So `1234567890` should yield `'1 billion 234 million 567 thousand 890'` -The program must also report any values that are out of range. It's -fine to stop at "trillion". +The program must also report any values that are out of range. +It's fine to stop at "trillion". ## Step 4 diff --git a/exercises/practice/scale-generator/.docs/instructions.md b/exercises/practice/scale-generator/.docs/instructions.md index 8a952e29c6..23e06e1ab2 100644 --- a/exercises/practice/scale-generator/.docs/instructions.md +++ b/exercises/practice/scale-generator/.docs/instructions.md @@ -2,23 +2,19 @@ ## Chromatic Scales -Scales in Western music are based on the chromatic (12-note) scale. This -scale can be expressed as the following group of pitches: +Scales in Western music are based on the chromatic (12-note) scale. +This scale can be expressed as the following group of pitches: > A, A♯, B, C, C♯, D, D♯, E, F, F♯, G, G♯ -A given sharp note (indicated by a ♯) can also be expressed as the flat -of the note above it (indicated by a ♭) so the chromatic scale can also be -written like this: +A given sharp note (indicated by a ♯) can also be expressed as the flat of the note above it (indicated by a ♭) so the chromatic scale can also be written like this: > A, B♭, B, C, D♭, D, E♭, E, F, G♭, G, A♭ -The major and minor scale and modes are subsets of this twelve-pitch -collection. They have seven pitches, and are called diatonic scales. -The collection of notes in these scales is written with either sharps or -flats, depending on the tonic (starting note). Here is a table indicating -whether the flat expression or sharp expression of the scale would be used for -a given tonic: +The major and minor scale and modes are subsets of this twelve-pitch collection. +They have seven pitches, and are called diatonic scales. +The collection of notes in these scales is written with either sharps or flats, depending on the tonic (starting note). +Here is a table indicating whether the flat expression or sharp expression of the scale would be used for a given tonic: | Key Signature | Major | Minor | | ------------- | --------------------- | -------------------- | @@ -26,46 +22,35 @@ a given tonic: | Sharp | G, D, A, E, B, F♯ | e, b, f♯, c♯, g♯, d♯ | | Flat | F, B♭, E♭, A♭, D♭, G♭ | d, g, c, f, b♭, e♭ | -Note that by common music theory convention the natural notes "C" and "a" -follow the sharps scale when ascending and the flats scale when descending. +Note that by common music theory convention the natural notes "C" and "a" follow the sharps scale when ascending and the flats scale when descending. For the scope of this exercise the scale is only ascending. ### Task Given a tonic, generate the 12 note chromatic scale starting with the tonic. -- Shift the base scale appropriately so that all 12 notes are returned -starting with the given tonic. -- For the given tonic, determine if the scale is to be returned with flats -or sharps. -- Return all notes in uppercase letters (except for the `b` for flats) -irrespective of the casing of the given tonic. +- Shift the base scale appropriately so that all 12 notes are returned starting with the given tonic. +- For the given tonic, determine if the scale is to be returned with flats or sharps. +- Return all notes in uppercase letters (except for the `b` for flats) irrespective of the casing of the given tonic. ## Diatonic Scales -The diatonic scales, and all other scales that derive from the -chromatic scale, are built upon intervals. An interval is the space -between two pitches. +The diatonic scales, and all other scales that derive from the chromatic scale, are built upon intervals. +An interval is the space between two pitches. -The simplest interval is between two adjacent notes, and is called a -"half step", or "minor second" (sometimes written as a lower-case "m"). -The interval between two notes that have an interceding note is called -a "whole step" or "major second" (written as an upper-case "M"). The -diatonic scales are built using only these two intervals between -adjacent notes. +The simplest interval is between two adjacent notes, and is called a "half step", or "minor second" (sometimes written as a lower-case "m"). +The interval between two notes that have an interceding note is called a "whole step" or "major second" (written as an upper-case "M"). +The diatonic scales are built using only these two intervals between adjacent notes. -Non-diatonic scales can contain other intervals. An "augmented second" -interval, written "A", has two interceding notes (e.g., from A to C or D♭ to E) -or a "whole step" plus a "half step". There are also smaller and larger -intervals, but they will not figure into this exercise. +Non-diatonic scales can contain other intervals. +An "augmented second" interval, written "A", has two interceding notes (e.g., from A to C or D♭ to E) or a "whole step" plus a "half step". +There are also smaller and larger intervals, but they will not figure into this exercise. ### Task -Given a tonic and a set of intervals, generate the musical scale starting with -the tonic and following the specified interval pattern. +Given a tonic and a set of intervals, generate the musical scale starting with the tonic and following the specified interval pattern. -This is similar to generating chromatic scales except that instead of returning -12 notes, you will return N+1 notes for N intervals. +This is similar to generating chromatic scales except that instead of returning 12 notes, you will return N+1 notes for N intervals. The first note is always the given tonic. Then, for each interval in the pattern, the next note is determined by starting from the previous note and skipping the number of notes indicated by the interval. diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index d4d57b80d5..2d6937ae96 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -3,8 +3,7 @@ > There are 10 types of people in the world: Those who understand > binary, and those who don't. -You and your fellow cohort of those in the "know" when it comes to -binary decide to come up with a secret "handshake". +You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake". ```text 00001 = wink @@ -12,7 +11,6 @@ binary decide to come up with a secret "handshake". 00100 = close your eyes 01000 = jump - 10000 = Reverse the order of the operations in the secret handshake. ``` @@ -20,8 +18,7 @@ Given a decimal number, convert it to the appropriate sequence of events for a s Here's a couple of examples: -Given the decimal input 3, the function would return the array -["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. +Given the decimal input 3, the function would return the array ["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. Let's now examine the input 19 which is 16+2+1 in powers of two and thus `10011` in binary. Recalling that the addition of 16 (`10000` in binary) reverses an array and that we already know what array is returned given input 3, the array returned for input 19 is ["double blink", "wink"]. diff --git a/exercises/practice/series/.docs/instructions.md b/exercises/practice/series/.docs/instructions.md index 3f9d371fa2..e32cc38c67 100644 --- a/exercises/practice/series/.docs/instructions.md +++ b/exercises/practice/series/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given a string of digits, output all the contiguous substrings of length `n` in -that string in the order that they appear. +Given a string of digits, output all the contiguous substrings of length `n` in that string in the order that they appear. For example, the string "49142" has the following 3-digit series: @@ -14,8 +13,7 @@ And the following 4-digit series: - "4914" - "9142" -And if you ask for a 6-digit series from a 5-digit string, you deserve -whatever you get. +And if you ask for a 6-digit series from a 5-digit string, you deserve whatever you get. -Note that these series are only required to occupy *adjacent positions* -in the input; the digits need not be *numerically consecutive*. +Note that these series are only required to occupy *adjacent positions* in the input; +the digits need not be *numerically consecutive*. diff --git a/exercises/practice/sgf-parsing/.docs/instructions.md b/exercises/practice/sgf-parsing/.docs/instructions.md index 7fc8e7f371..d38b341fc0 100644 --- a/exercises/practice/sgf-parsing/.docs/instructions.md +++ b/exercises/practice/sgf-parsing/.docs/instructions.md @@ -2,14 +2,15 @@ Parsing a Smart Game Format string. -[SGF](https://en.wikipedia.org/wiki/Smart_Game_Format) is a standard format for -storing board game files, in particular go. +[SGF][sgf] is a standard format for storing board game files, in particular go. SGF is a fairly simple format. An SGF file usually contains a single tree of nodes where each node is a property list. The property list contains key value pairs, each key can only occur once but may have multiple values. +The exercise will have you parse an SGF string and return a tree structure of properties. + An SGF file may look like this: ```text @@ -53,6 +54,24 @@ A key can have multiple values associated with it. For example: Here `AB` (add black) is used to add three black stones to the board. +All property values will be the [SGF Text type][sgf-text]. +You don't need to implement any other value type. +Although you can read the [full documentation of the Text type][sgf-text], a summary of the important points is below: + +- Newlines are removed if they come immediately after a `\`, otherwise they remain as newlines. +- All whitespace characters other than newline are converted to spaces. +- `\` is the escape character. + Any non-whitespace character after `\` is inserted as-is. + Any whitespace character after `\` follows the above rules. + Note that SGF does **not** have escape sequences for whitespace characters such as `\t` or `\n`. + +Be careful not to get confused between: + +- The string as it is represented in a string literal in the tests +- The string that is passed to the SGF parser + +Escape sequences in the string literals may have already been processed by the programming language's parser before they are passed to the SGF parser. + There are a few more complexities to SGF (and parsing in general), which you can mostly ignore. You should assume that the input is encoded in UTF-8, the tests won't contain a charset property, so don't worry about @@ -60,7 +79,5 @@ that. Furthermore you may assume that all newlines are unix style (`\n`, no `\r` or `\r\n` will be in the tests) and that no optional whitespace between properties, nodes, etc will be in the tests. -The exercise will have you parse an SGF string and return a tree -structure of properties. You do not need to encode knowledge about the -data types of properties, just use the rules for the -[text](http://www.red-bean.com/sgf/sgf4.html#text) type everywhere. +[sgf]: https://en.wikipedia.org/wiki/Smart_Game_Format +[sgf-text]: https://www.red-bean.com/sgf/sgf4.html#text diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 8384059d84..c3c0abeb84 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -3,12 +3,12 @@ Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number. -The Sieve of Eratosthenes is a simple, ancient algorithm for finding all -prime numbers up to any given limit. It does so by iteratively marking as -composite (i.e. not prime) the multiples of each prime, starting with the -multiples of 2. It does not use any division or remainder operation. +The Sieve of Eratosthenes is a simple, ancient algorithm for finding all prime numbers up to any given limit. +It does so by iteratively marking as composite (i.e. not prime) the multiples of each prime, starting with the multiples of 2. +It does not use any division or remainder operation. -Create your range, starting at two and continuing up to and including the given limit. (i.e. [2, limit]) +Create your range, starting at two and continuing up to and including the given limit. +(i.e. [2, limit]) The algorithm consists of repeating the following over and over: @@ -20,11 +20,9 @@ Repeat until you have processed each number in your range. When the algorithm terminates, all the numbers in the list that have not been marked are prime. -The wikipedia article has a useful graphic that explains the algorithm: -[https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) +[This wikipedia article][eratosthenes] has a useful graphic that explains the algorithm. -Notice that this is a very specific algorithm, and the tests don't check -that you've implemented the algorithm, only that you've come up with the -correct list of primes. A good first test is to check that you do not use -division or remainder operations (div, /, mod or % depending on the -language). +Notice that this is a very specific algorithm, and the tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. +A good first test is to check that you do not use division or remainder operations (div, /, mod or % depending on the language). + +[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 22a7e4d4bd..9167a1d33a 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -4,42 +4,34 @@ Implement a simple shift cipher like Caesar and a more secure substitution ciphe ## Step 1 -"If he had anything confidential to say, he wrote it in cipher, that is, -by so changing the order of the letters of the alphabet, that not a word -could be made out. If anyone wishes to decipher these, and get at their -meaning, he must substitute the fourth letter of the alphabet, namely D, -for A, and so with the others." +"If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out. +If anyone wishes to decipher these, and get at their meaning, he must substitute the fourth letter of the alphabet, namely D, for A, and so with the others." —Suetonius, Life of Julius Caesar -Ciphers are very straight-forward algorithms that allow us to render -text less readable while still allowing easy deciphering. They are -vulnerable to many forms of cryptanalysis, but we are lucky that -generally our little sisters are not cryptanalysts. +Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. +They are vulnerable to many forms of cryptanalysis, but we are lucky that generally our little sisters are not cryptanalysts. -The Caesar Cipher was used for some messages from Julius Caesar that -were sent afield. Now Caesar knew that the cipher wasn't very good, but -he had one ally in that respect: almost nobody could read well. So even -being a couple letters off was sufficient so that people couldn't -recognize the few words that they did know. +The Caesar Cipher was used for some messages from Julius Caesar that were sent afield. +Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. +So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know. Your task is to create a simple shift cipher like the Caesar Cipher. This image is a great example of the Caesar Cipher: -![Caesar Cipher][1] +![Caesar Cipher][img-caesar-cipher] For example: -Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". Obscure enough to keep our message secret in transit. +Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". +Obscure enough to keep our message secret in transit. -When "ldpdsdqgdehdu" is put into the decode function it would return -the original "iamapandabear" letting your friend read your original -message. +When "ldpdsdqgdehdu" is put into the decode function it would return the original "iamapandabear" letting your friend read your original message. ## Step 2 -Shift ciphers are no fun though when your kid sister figures it out. Try -amending the code to allow us to specify a key and use that for the -shift distance. This is called a substitution cipher. +Shift ciphers are no fun though when your kid sister figures it out. +Try amending the code to allow us to specify a key and use that for the shift distance. +This is called a substitution cipher. Here's an example: @@ -49,31 +41,26 @@ would return the original "iamapandabear". Given the key "ddddddddddddddddd", encoding our string "iamapandabear" would return the obscured "ldpdsdqgdehdu" -In the example above, we've set a = 0 for the key value. So when the -plaintext is added to the key, we end up with the same message coming -out. So "aaaa" is not an ideal key. But if we set the key to "dddd", we -would get the same thing as the Caesar Cipher. +In the example above, we've set a = 0 for the key value. +So when the plaintext is added to the key, we end up with the same message coming out. +So "aaaa" is not an ideal key. +But if we set the key to "dddd", we would get the same thing as the Caesar Cipher. ## Step 3 -The weakest link in any cipher is the human being. Let's make your -substitution cipher a little more fault tolerant by providing a source -of randomness and ensuring that the key contains only lowercase letters. +The weakest link in any cipher is the human being. +Let's make your substitution cipher a little more fault tolerant by providing a source of randomness and ensuring that the key contains only lowercase letters. -If someone doesn't submit a key at all, generate a truly random key of -at least 100 lowercase characters in length. +If someone doesn't submit a key at all, generate a truly random key of at least 100 lowercase characters in length. ## Extensions -Shift ciphers work by making the text slightly odd, but are vulnerable -to frequency analysis. Substitution ciphers help that, but are still -very vulnerable when the key is short or if spaces are preserved. Later -on you'll see one solution to this problem in the exercise -"crypto-square". +Shift ciphers work by making the text slightly odd, but are vulnerable to frequency analysis. +Substitution ciphers help that, but are still very vulnerable when the key is short or if spaces are preserved. +Later on you'll see one solution to this problem in the exercise "crypto-square". -If you want to go farther in this field, the questions begin to be about -how we can exchange keys in a secure way. Take a look at [Diffie-Hellman -on Wikipedia][dh] for one of the first implementations of this scheme. +If you want to go farther in this field, the questions begin to be about how we can exchange keys in a secure way. +Take a look at [Diffie-Hellman on Wikipedia][dh] for one of the first implementations of this scheme. -[1]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png -[dh]: http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +[img-caesar-cipher]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png +[dh]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange diff --git a/exercises/practice/simple-linked-list/.docs/instructions.md b/exercises/practice/simple-linked-list/.docs/instructions.md index 1c9d0b3de9..4d845fac06 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.md @@ -2,21 +2,14 @@ Write a simple linked list implementation that uses Elements and a List. -The linked list is a fundamental data structure in computer science, -often used in the implementation of other data structures. They're -pervasive in functional programming languages, such as Clojure, Erlang, -or Haskell, but far less common in imperative languages such as Ruby or -Python. +The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. +They're pervasive in functional programming languages, such as Clojure, Erlang, or Haskell, but far less common in imperative languages such as Ruby or Python. -The simplest kind of linked list is a singly linked list. Each element in the -list contains data and a "next" field pointing to the next element in the list -of elements. +The simplest kind of linked list is a singly linked list. +Each element in the list contains data and a "next" field pointing to the next element in the list of elements. -This variant of linked lists is often used to represent sequences or -push-down stacks (also called a LIFO stack; Last In, First Out). +This variant of linked lists is often used to represent sequences or push-down stacks (also called a LIFO stack; Last In, First Out). -As a first take, lets create a singly linked list to contain the range (1..10), -and provide functions to reverse a linked list and convert to and from arrays. +As a first take, lets create a singly linked list to contain the range (1..10), and provide functions to reverse a linked list and convert to and from arrays. -When implementing this in a language with built-in linked lists, -implement your own abstract data type. +When implementing this in a language with built-in linked lists, implement your own abstract data type. diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md index 9e48f0ecd1..405a6dfb46 100644 --- a/exercises/practice/space-age/.docs/instructions.md +++ b/exercises/practice/space-age/.docs/instructions.md @@ -14,5 +14,6 @@ Given an age in seconds, calculate how old someone would be on: So if you were told someone were 1,000,000,000 seconds old, you should be able to say that they're 31.69 Earth-years old. -If you're wondering why Pluto didn't make the cut, go watch [this -YouTube video](http://www.youtube.com/watch?v=Z_2gbGXzFbs). +If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. + +[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs diff --git a/exercises/practice/spiral-matrix/.docs/instructions.md b/exercises/practice/spiral-matrix/.docs/instructions.md index 0e7674ff11..ba99e12c73 100644 --- a/exercises/practice/spiral-matrix/.docs/instructions.md +++ b/exercises/practice/spiral-matrix/.docs/instructions.md @@ -2,9 +2,7 @@ Given the size, return a square matrix of numbers in spiral order. -The matrix should be filled with natural numbers, starting from 1 -in the top-left corner, increasing in an inward, clockwise spiral order, -like these examples: +The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples: ## Examples diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index bb512396aa..ff7fdffd86 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -1,9 +1,7 @@ # Instructions -Given a number, find the sum of all the unique multiples of particular numbers up to -but not including that number. +Given a number, find the sum of all the unique multiples of particular numbers up to but not including that number. -If we list all the natural numbers below 20 that are multiples of 3 or 5, -we get 3, 5, 6, 9, 10, 12, 15, and 18. +If we list all the natural numbers below 20 that are multiples of 3 or 5, we get 3, 5, 6, 9, 10, 12, 15, and 18. The sum of these multiples is 78. diff --git a/exercises/practice/tournament/.docs/instructions.md b/exercises/practice/tournament/.docs/instructions.md index 8831dd195e..e5ca237385 100644 --- a/exercises/practice/tournament/.docs/instructions.md +++ b/exercises/practice/tournament/.docs/instructions.md @@ -2,8 +2,7 @@ Tally the results of a small football competition. -Based on an input file containing which team played against which and what the -outcome was, create a file with a table like this: +Based on an input file containing which team played against which and what the outcome was, create a file with a table like this: ```text Team | MP | W | D | L | P @@ -21,9 +20,12 @@ What do those abbreviations mean? - L: Matches Lost - P: Points -A win earns a team 3 points. A draw earns 1. A loss earns 0. +A win earns a team 3 points. +A draw earns 1. +A loss earns 0. -The outcome should be ordered by points, descending. In case of a tie, teams are ordered alphabetically. +The outcome is ordered by points, descending. +In case of a tie, teams are ordered alphabetically. ## Input @@ -38,7 +40,8 @@ Blithering Badgers;Devastating Donkeys;loss Allegoric Alaskans;Courageous Californians;win ``` -The result of the match refers to the first team listed. So this line: +The result of the match refers to the first team listed. +So this line: ```text Allegoric Alaskans;Blithering Badgers;win diff --git a/exercises/practice/transpose/.docs/instructions.md b/exercises/practice/transpose/.docs/instructions.md index c0e1d14a54..6033af745f 100644 --- a/exercises/practice/transpose/.docs/instructions.md +++ b/exercises/practice/transpose/.docs/instructions.md @@ -17,7 +17,8 @@ BE CF ``` -Rows become columns and columns become rows. See . +Rows become columns and columns become rows. +See [transpose][]. If the input has rows of different lengths, this is to be solved as follows: @@ -55,5 +56,6 @@ BE ``` In general, all characters from the input should also be present in the transposed output. -That means that if a column in the input text contains only spaces on its bottom-most row(s), -the corresponding output row should contain the spaces in its right-most column(s). +That means that if a column in the input text contains only spaces on its bottom-most row(s), the corresponding output row should contain the spaces in its right-most column(s). + +[transpose]: https://en.wikipedia.org/wiki/Transpose diff --git a/exercises/practice/tree-building/.docs/instructions.md b/exercises/practice/tree-building/.docs/instructions.md index 32c6087b43..0148e8a010 100644 --- a/exercises/practice/tree-building/.docs/instructions.md +++ b/exercises/practice/tree-building/.docs/instructions.md @@ -2,17 +2,14 @@ Refactor a tree building algorithm. -Some web-forums have a tree layout, so posts are presented as a tree. However -the posts are typically stored in a database as an unsorted set of records. Thus -when presenting the posts to the user the tree structure has to be -reconstructed. +Some web-forums have a tree layout, so posts are presented as a tree. +However the posts are typically stored in a database as an unsorted set of records. +Thus when presenting the posts to the user the tree structure has to be reconstructed. -Your job will be to refactor a working but slow and ugly piece of code that -implements the tree building logic for highly abstracted records. The records -only contain an ID number and a parent ID number. The ID number is always -between 0 (inclusive) and the length of the record list (exclusive). All records -have a parent ID lower than their own ID, except for the root record, which has -a parent ID that's equal to its own ID. +Your job will be to refactor a working but slow and ugly piece of code that implements the tree building logic for highly abstracted records. +The records only contain an ID number and a parent ID number. +The ID number is always between 0 (inclusive) and the length of the record list (exclusive). +All records have a parent ID lower than their own ID, except for the root record, which has a parent ID that's equal to its own ID. An example tree: diff --git a/exercises/practice/triangle/.docs/instructions.md b/exercises/practice/triangle/.docs/instructions.md index af1060f6ea..ac39008726 100644 --- a/exercises/practice/triangle/.docs/instructions.md +++ b/exercises/practice/triangle/.docs/instructions.md @@ -4,9 +4,8 @@ Determine if a triangle is equilateral, isosceles, or scalene. An _equilateral_ triangle has all three sides the same length. -An _isosceles_ triangle has at least two sides the same length. (It is sometimes -specified as having exactly two sides the same length, but for the purposes of -this exercise we'll say at least two.) +An _isosceles_ triangle has at least two sides the same length. +(It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) A _scalene_ triangle has all sides of different lengths. @@ -16,7 +15,8 @@ For a shape to be a triangle at all, all sides have to be of length > 0, and the In equations: -Let `a`, `b`, and `c` be sides of the triangle. Then all three of the following expressions must be true: +Let `a`, `b`, and `c` be sides of the triangle. +Then all three of the following expressions must be true: ```text a + b ≥ c @@ -24,10 +24,6 @@ b + c ≥ a a + c ≥ b ``` -See [Triangle Inequality](https://en.wikipedia.org/wiki/Triangle_inequality). +See [Triangle Inequality][triangle-inequality] -## Dig Deeper - -The case where the sum of the lengths of two sides _equals_ that of the -third is known as a _degenerate_ triangle - it has zero area and looks like -a single line. Feel free to add your own code/tests to check for degenerate triangles. +[triangle-inequality]: https://en.wikipedia.org/wiki/Triangle_inequality diff --git a/exercises/practice/twelve-days/.docs/instructions.md b/exercises/practice/twelve-days/.docs/instructions.md index ce43aa3034..83bb6e1926 100644 --- a/exercises/practice/twelve-days/.docs/instructions.md +++ b/exercises/practice/twelve-days/.docs/instructions.md @@ -2,7 +2,8 @@ Your task in this exercise is to write code that returns the lyrics of the song: "The Twelve Days of Christmas." -"The Twelve Days of Christmas" is a common English Christmas carol. Each subsequent verse of the song builds on the previous verse. +"The Twelve Days of Christmas" is a common English Christmas carol. +Each subsequent verse of the song builds on the previous verse. The lyrics your code returns should _exactly_ match the full song text shown below. diff --git a/exercises/practice/two-bucket/.docs/instructions.md b/exercises/practice/two-bucket/.docs/instructions.md index 01c58ad773..7249deb361 100644 --- a/exercises/practice/two-bucket/.docs/instructions.md +++ b/exercises/practice/two-bucket/.docs/instructions.md @@ -29,9 +29,18 @@ Your program should determine: Note: any time a change is made to either or both buckets counts as one (1) action. Example: -Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. Let's say at a given step, bucket one is holding 7 liters and bucket two is holding 8 liters (7,8). If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one action. Instead, if you had poured from bucket one into bucket two until bucket two was full, resulting in 4 liters in bucket one and 11 liters in bucket two (4,11), that would also only count as one action. +Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. +Let's say at a given step, bucket one is holding 7 liters and bucket two is holding 8 liters (7,8). +If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one action. +Instead, if you had poured from bucket one into bucket two until bucket two was full, resulting in 4 liters in bucket one and 11 liters in bucket two (4,11), that would also only count as one action. Another Example: -Bucket one can hold 3 liters, and bucket two can hold up to 5 liters. You are told you must start with bucket one. So your first action is to fill bucket one. You choose to empty bucket one for your second action. For your third action, you may not fill bucket two, because this violates the third rule -- you may not end up in a state after any action where the starting bucket is empty and the other bucket is full. +Bucket one can hold 3 liters, and bucket two can hold up to 5 liters. +You are told you must start with bucket one. +So your first action is to fill bucket one. +You choose to empty bucket one for your second action. +For your third action, you may not fill bucket two, because this violates the third rule -- you may not end up in a state after any action where the starting bucket is empty and the other bucket is full. -Written with <3 at [Fullstack Academy](http://www.fullstackacademy.com/) by Lindsay Levine. +Written with <3 at [Fullstack Academy][fullstack] by Lindsay Levine. + +[fullstack]: https://www.fullstackacademy.com/ diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md index f4853c54de..bdd72bde11 100644 --- a/exercises/practice/two-fer/.docs/instructions.md +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -1,6 +1,7 @@ # Instructions -`Two-fer` or `2-fer` is short for two for one. One for you and one for me. +`Two-fer` or `2-fer` is short for two for one. +One for you and one for me. Given a name, return a string with the message: diff --git a/exercises/practice/variable-length-quantity/.docs/instructions.md b/exercises/practice/variable-length-quantity/.docs/instructions.md index eadce28d0e..5012548268 100644 --- a/exercises/practice/variable-length-quantity/.docs/instructions.md +++ b/exercises/practice/variable-length-quantity/.docs/instructions.md @@ -2,10 +2,10 @@ Implement variable length quantity encoding and decoding. -The goal of this exercise is to implement [VLQ](https://en.wikipedia.org/wiki/Variable-length_quantity) encoding/decoding. +The goal of this exercise is to implement [VLQ][vlq] encoding/decoding. In short, the goal of this encoding is to encode integer values in a way that would save bytes. -Only the first 7 bits of each byte is significant (right-justified; sort of like an ASCII byte). +Only the first 7 bits of each byte are significant (right-justified; sort of like an ASCII byte). So, if you have a 32-bit value, you have to unpack it into a series of 7-bit bytes. Of course, you will have a variable number of bytes depending upon your integer. To indicate which is the last byte of the series, you leave bit #7 clear. @@ -30,3 +30,5 @@ Here are examples of integers as 32-bit values, and the variable length quantiti 08000000 C0 80 80 00 0FFFFFFF FF FF FF 7F ``` + +[vlq]: https://en.wikipedia.org/wiki/Variable-length_quantity diff --git a/exercises/practice/word-count/.docs/instructions.md b/exercises/practice/word-count/.docs/instructions.md index d3548e5d88..8b7f03ede7 100644 --- a/exercises/practice/word-count/.docs/instructions.md +++ b/exercises/practice/word-count/.docs/instructions.md @@ -12,7 +12,7 @@ When counting words you can assume the following rules: 1. The count is _case insensitive_ (ie "You", "you", and "YOU" are 3 uses of the same word) 2. The count is _unordered_; the tests will ignore how words and counts are ordered -3. Other than the apostrophe in a _contraction_ all forms of _punctuation_ are ignored +3. Other than the apostrophe in a _contraction_ all forms of _punctuation_ are regarded as spaces 4. The words can be separated by _any_ form of whitespace (ie "\t", "\n", " ") For example, for the phrase `"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` the count would be: diff --git a/exercises/practice/word-search/.docs/instructions.md b/exercises/practice/word-search/.docs/instructions.md index 345fa592ef..e2d08aa9ee 100644 --- a/exercises/practice/word-search/.docs/instructions.md +++ b/exercises/practice/word-search/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -In word search puzzles you get a square of letters and have to find specific -words in them. +In word search puzzles you get a square of letters and have to find specific words in them. For example: @@ -20,8 +19,6 @@ clojurermt There are several programming languages hidden in the above square. -Words can be hidden in all kinds of directions: left-to-right, right-to-left, -vertical and diagonal. +Words can be hidden in all kinds of directions: left-to-right, right-to-left, vertical and diagonal. -Given a puzzle and a list of words return the location of the first and last -letter of each word. +Given a puzzle and a list of words return the location of the first and last letter of each word. diff --git a/exercises/practice/wordy/.docs/instructions.md b/exercises/practice/wordy/.docs/instructions.md index f65b05acf5..0b9e67b6ca 100644 --- a/exercises/practice/wordy/.docs/instructions.md +++ b/exercises/practice/wordy/.docs/instructions.md @@ -40,8 +40,7 @@ Now, perform the other three operations. Handle a set of operations, in sequence. -Since these are verbal word problems, evaluate the expression from -left-to-right, _ignoring the typical order of operations._ +Since these are verbal word problems, evaluate the expression from left-to-right, _ignoring the typical order of operations._ > What is 5 plus 13 plus 6? @@ -55,14 +54,6 @@ left-to-right, _ignoring the typical order of operations._ The parser should reject: -* Unsupported operations ("What is 52 cubed?") -* Non-math questions ("Who is the President of the United States") -* Word problems with invalid syntax ("What is 1 plus plus 2?") - -## Bonus — Exponentials - -If you'd like, handle exponentials. - -> What is 2 raised to the 5th power? - -32 +- Unsupported operations ("What is 52 cubed?") +- Non-math questions ("Who is the President of the United States") +- Word problems with invalid syntax ("What is 1 plus plus 2?") diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md index d5b2b283ea..163ba3792c 100644 --- a/exercises/practice/yacht/.docs/instructions.md +++ b/exercises/practice/yacht/.docs/instructions.md @@ -1,10 +1,8 @@ # Instructions -The dice game [Yacht](https://en.wikipedia.org/wiki/Yacht_(dice_game)) is from -the same family as Poker Dice, Generala and particularly Yahtzee, of which it -is a precursor. In the game, five dice are rolled and the result can be entered -in any of twelve categories. The score of a throw of the dice depends on -category chosen. +The dice game [Yacht][yacht] is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. +In the game, five dice are rolled and the result can be entered in any of twelve categories. +The score of a throw of the dice depends on category chosen. ## Scores in Yacht @@ -24,13 +22,14 @@ category chosen. | 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. +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. ## Task -Given a list of values for five dice and a category, your solution should return -the score of the dice for that category. If the dice do not satisfy the requirements -of the category your solution should return 0. You can assume that five values -will always be presented, and the value of each will be between one and six -inclusively. You should not assume that the dice are ordered. +Given a list of values for five dice and a category, your solution should return the score of the dice for that category. +If the dice do not satisfy the requirements of the category your solution should return 0. +You can assume that five values will always be presented, and the value of each will be between one and six inclusively. +You should not assume that the dice are ordered. + +[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) diff --git a/exercises/practice/zebra-puzzle/.docs/instructions.md b/exercises/practice/zebra-puzzle/.docs/instructions.md index 18a8da1d87..6d62d18e4c 100644 --- a/exercises/practice/zebra-puzzle/.docs/instructions.md +++ b/exercises/practice/zebra-puzzle/.docs/instructions.md @@ -18,9 +18,7 @@ Solve the zebra puzzle. 14. The Japanese smokes Parliaments. 15. The Norwegian lives next to the blue house. -Each of the five houses is painted a different color, and their -inhabitants are of different national extractions, own different pets, -drink different beverages and smoke different brands of cigarettes. +Each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and smoke different brands of cigarettes. Which of the residents drinks water? Who owns the zebra? diff --git a/exercises/practice/zipper/.docs/instructions.md b/exercises/practice/zipper/.docs/instructions.md index d760aa0dd8..5445db0035 100644 --- a/exercises/practice/zipper/.docs/instructions.md +++ b/exercises/practice/zipper/.docs/instructions.md @@ -2,13 +2,10 @@ Creating a zipper for a binary tree. -[Zippers](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29) are -a purely functional way of navigating within a data structure and -manipulating it. They essentially contain a data structure and a -pointer into that data structure (called the focus). +[Zippers][zipper] are a purely functional way of navigating within a data structure and manipulating it. +They essentially contain a data structure and a pointer into that data structure (called the focus). -For example given a rose tree (where each node contains a value and a -list of child nodes) a zipper might support these operations: +For example given a rose tree (where each node contains a value and a list of child nodes) a zipper might support these operations: - `from_tree` (get a zipper out of a rose tree, the focus is on the root node) - `to_tree` (get the rose tree out of the zipper) @@ -26,3 +23,5 @@ list of child nodes) a zipper might support these operations: - `delete` (removes the focus node and all subtrees, focus moves to the `next` node if possible otherwise to the `prev` node if possible, otherwise to the parent node, returns a new zipper) + +[zipper]: https://en.wikipedia.org/wiki/Zipper_%28data_structure%29 From 40de059a6a4b67409e2b8be8163fd22a228cdedd Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 20 Nov 2022 10:25:55 -0500 Subject: [PATCH 039/826] Corrected parameter type stated in docstring --- exercises/concept/cater-waiter/sets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/cater-waiter/sets.py b/exercises/concept/cater-waiter/sets.py index faa0114d9c..b0202e6a5f 100644 --- a/exercises/concept/cater-waiter/sets.py +++ b/exercises/concept/cater-waiter/sets.py @@ -14,7 +14,7 @@ def clean_ingredients(dish_name, dish_ingredients): """Remove duplicates from `dish_ingredients`. :param dish_name: str - containing the dish name. - :param dish_ingredients: set - dish ingredients. + :param dish_ingredients: list - dish ingredients. :return: tuple - containing (dish_name, ingredient set). This function should return a `tuple` with the name of the dish as the first item, From 51b91d3796a63463982506361bf511825cb558c6 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Thu, 24 Nov 2022 08:30:37 +0100 Subject: [PATCH 040/826] [New Concept Exercise] : Linus the Locomotive Engineer (Packing, Unpacking and Multiple Assignment) (#3212) * Update introduction.md * Update about.md * Added authers * Added filles for exercise * Progress * Merged 2 and 3 * typo fix * Fix file_names and added tests for 1 exericse * Update locomotive_engineer.py * Added zip, added tests for exercise 2 * Adds test for exercise for 3 and 4 * fixes * Added fifth exercise * Fix typo * Adding Exercise Slug to Config.json Added exercise to track config. * Blurb and Design Doc Expanded the blurb and added in the design notes. * Placeholder for concept intro and task changes Put in placeholder for concept intro (still needs editing down). Re-arranged task 2. Attempted to re-arrange task 5 (still working on it) * fixes * Further updates to task 5 * fix * Updated test for exericse 5 * Update instructions.md * uypdated exercise 3 * Updated task instructions * removed notes * Added more tests for exercise 5 * Updated exercise intro and concept intro * Added some more hints * Added more hints * some more * Added blurb * Added Links Also edited exemplar and a few other things. * Small updates Rephrased the unpacking definition across about.md, introduction.md, and the concept blurb. * spell fixes * spell fix * Replaced array with list * fix typo * Swapped although * fix typos * fix * Grammar and Rephrasing for About.md * additional edits for *args **kwargs * Adjustments line 273 and 145 * Update about.md * Final Edits Final edits before community review. Includes multiple potential solutions to task 5. When done reviewing/selecting the code for the exemplar, please delete the code in the stub file and un-comment the stubbed functions and docstrings. Thanks! * Blurb Edit `dictionary` to `dict`. * UnEdit Forgot we can't use backticks in a blurb, so needed to remove them. * Clean up * Uncomment stub Co-authored-by: BethanyG --- .../.meta/config.json | 4 +- .../about.md | 355 +++++++++++++++++- .../introduction.md | 22 +- .../links.json | 24 +- config.json | 8 + .../locomotive-engineer/.docs/hints.md | 35 ++ .../locomotive-engineer/.docs/instructions.md | 110 ++++++ .../locomotive-engineer/.docs/introduction.md | 289 ++++++++++++++ .../locomotive-engineer/.meta/config.json | 11 + .../locomotive-engineer/.meta/design.md | 69 ++++ .../locomotive-engineer/.meta/exemplar.py | 48 +++ .../locomotive_engineer.py | 38 ++ .../locomotive_engineer_test.py | 82 ++++ 13 files changed, 1083 insertions(+), 12 deletions(-) create mode 100644 exercises/concept/locomotive-engineer/.docs/hints.md create mode 100644 exercises/concept/locomotive-engineer/.docs/instructions.md create mode 100644 exercises/concept/locomotive-engineer/.docs/introduction.md create mode 100644 exercises/concept/locomotive-engineer/.meta/config.json create mode 100644 exercises/concept/locomotive-engineer/.meta/design.md create mode 100644 exercises/concept/locomotive-engineer/.meta/exemplar.py create mode 100644 exercises/concept/locomotive-engineer/locomotive_engineer.py create mode 100644 exercises/concept/locomotive-engineer/locomotive_engineer_test.py diff --git a/concepts/unpacking-and-multiple-assignment/.meta/config.json b/concepts/unpacking-and-multiple-assignment/.meta/config.json index 9b9e8da5a9..ecf013c7ac 100644 --- a/concepts/unpacking-and-multiple-assignment/.meta/config.json +++ b/concepts/unpacking-and-multiple-assignment/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "Unpacking is the process of extracting individual elements of a collection, such as a list, tuple, or dictionary by iterating over them. Unpacked values can be assigned to variables within the same step. Multiple assignment is the ability to assign values to multiple variables in one line.", + "authors": ["meatball","bethanyg"], "contributors": [] } diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index c628150d56..68c8e0662b 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -1,2 +1,355 @@ -#TODO: Add about for this concept. +# Unpacking and Multiple Assignment +Unpacking refers to the act of extracting the elements of a collection, such as a `list`, `tuple`, or `dict`, using iteration. +Unpacked values can be assigned to variables within the same step. +With unpacking, there are some special operators used: `*` and `**`. +When unpacking a list or tuple, the `*` operator can be used to assign all the remaining elements to a variable. +When unpacking a dictionary, the `**` operator can be used to assign all the remaining key-value pairs to a variable. + +When these operators are used without a collection they will _pack_ a number of values into a `list`, `tuple`, or dictionary. +It is common to use this kind of behavior when creating functions that take an arbitrary number of arguments. + +Multiple assignment is the ability to assign multiple variables in one line. +This is done by separating the variables with a comma. + +```exercism/caution +`*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. +``` + +## Multiple assignment + +[Multiple assignment][multiple assignment] is the ability to assign multiple variables in one line. +This allows for code to be more concise and readable. +There has to be x number of variables on the left side of the `=` sign and x number of values on the right side of the `=` sign. +To separate the values, use a comma `,`: + +```python +>>> a, b = 1, 2 +>>> a +1 +``` + +Multiple assignment is not limited to one data type but can instead be used with any data type. +For example: + +```python +>>> x, y, z = 1, "Hello", True +>>> x +1 + +>>> y +'Hello' + +>>> z +True +``` + +Multiple assignment also allows for the swapping of variables in `lists`. +This practice is pretty common in [linear sorting algorithms][sorting algorithms]. +For example: + +```python +>>> numbers = [1, 2] +>>> numbers[0], numbers[1] = numbers[1], numbers[0] +>>> numbers +[2, 1] +``` + +It is also possible to assign multiple variables to the same value: + +```python +>>> a = b = 1 +>>> a +1 +>>> b +1 +``` + +## Unpacking + +```exercism/note +The examples below use lists but the same concepts apply to tuples. +``` + +In Python, it is possible to [unpack a `list`/`tuple`/`dictionary`][unpacking] into distinct variables. +Since values appear within lists in a specific order, it is therefore possible to _unpack_ a `list` into variables in the same order. + +Unpacking a list into variables: + +```python +>>> fruits = ["apple", "banana", "cherry"] +>>> x, y, z = fruits +>>> x +"apple" +``` + +If there are values that are not needed then you can use `_` to ignore those values: + +```python +>>> fruits = ["apple", "banana", "cherry"] +>>> _, _, z = fruits +>>> z +"cherry" +``` + +You can also do [deep unpacking][deep unpacking] on a `list`, which assigns values from a `list` within a `list` (_this is also known as nested list unpacking_): + +```python +>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] +>>> [[a, b], [c, d]] = fruits_vegetables +>>> a +"apple" + +>>> d +"potato" +``` + +Deep unpacking and normal unpacking can be mixed together: + +```python +>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] +>>> [a, [c, d]] = fruits_vegetables +>>> a +["apple", "banana"] + +>>> c +"carrot" +``` + +If the unpacking has variables with incorrect placement and/or an incorrect number of values, you will get a `ValueError`: + +```python +>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] +>>> [[a, b], [d]] = fruits_vegetables + +ValueError: too many values to unpack (expected 1) +``` + +### Unpacking a list/tuple with `*` + +When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the remainder values. +This can be used instead of slicing the `list`/`tuple`, which in some situations could be more readable. +For example, we can extract the first element below and then pack the remaining values into a new `list` without the first element: + +```python +>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] +>>> x, *last = fruits +>>> x +"apple" + +>>> last +["banana", "cherry", "orange", "kiwi", "melon", "mango"] +``` + +We can also extract the values at the beginning and end of the `list` while grouping all the values in the middle: + +```python +>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] +>>> x, *middle, y, z = fruits +>>> y +"melon" + +>>> middle +["banana", "cherry", "orange", "kiwi"] +``` + +We can also use `*` in deep unpacking: + +```python +>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]] +>>> [[a, *rest], b] = fruits_vegetables +>>> a +"apple" + +>>> rest +["banana", "melon"] +``` + +### Unpacking a dictionary + +[Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`. +Iteration over dictionaries defaults to the `keys`. +So when unpacking a `dict`, you can only unpack the `keys` and not the `values`: + +```python +>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} +>>> x, y, z = fruits_inventory +>>> x +"apple" +``` + +If you want to unpack the values then you can use the `values()` method: + +```python +>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} +>>> x, y, z = fruits_inventory.values() +>>> x +6 +``` + +If both `keys` and `values` are needed, use the `items()` method. +Using `items()` will generate tuples with `key-value` pairs. +This is because [`dict.items()` generates a `tuple`][items] and within it there is a `tuple` for each `key-value` pair:. + +```python +>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} +>>> x, y, z = fruits_inventory.items() +>>> x +("apple", 6) +``` + +## Packing + +As with unpacking, _packing_ uses the same `*` and `**` operators. +[Packing][packing and unpacking]] is the ability to group multiple values into one variable. +This is useful for when you want to _unpack_ values, make changes, and then _pack_ the results back into a variable. +It also makes it possible to perform merges on 2 or more `lists`/`tuples`/`dicts`. + +### Packing a list/tuple with `*` + +Packing a `list`/`tuple` is done by using the `*` operator +This will pack all the variables into a list/tuple. + +```python +>>> fruits = ["apple", "banana", "cherry"] +>>> more_fruits = ["orange", "kiwi", "melon", "mango"] +>>> combined_fruits_lists = [*fruits, *more_fruits] + +>>> combined_fruits_lists +["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] +``` + +### Packing a dictionary with `**` + +Packing a dictionary is done by using the `**` operator. +This will pack all the variables into a dictionary. + +```python +>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} +>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3} +>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory} + +>>> combined_fruits_inventory +{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3} +``` + +## Usage of `*` and `**` with a function + +### Packing with function parameters + +When you have a function that accepts an arbitrary or large number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] to pack or group those parameters together. +`*args` is used for packing/signaling an arbitrary number of positional (non-keyworded) arguments. +`**kwargs` is used for packing/signaling an arbitrary number of keyword arguments to a function. + +Usage of `*args`: + +```python +>>> def my_function(*args): +... print(args) + +>>> my_function(1, 2, 3) +(1, 2, 3) + +>>> my_function("Hello", "World") +("Hello", "World") + +>>> my_function(1, 2, 3, "Hello", "Mars") +(1, 2, 3, "Hello", "Mars") +``` + +Usage of `**kwargs`: + +```python +>>> def my_function(**kwargs): +... print(kwargs) + +>>> my_function(a=1, b=2, c=3) +{"a": 1, "b": 2, "c": 3} +``` + +`*args` and `**kwargs` can also be used in combination with one another: + +```python +>>> def my_function(*args, **kwargs): +... print(sum(args)) +... for key, value in kwargs.items(): +... print(f"{key} = {value}") + +>>> my_function(1, 2, 3, a=1, b=2, c=3) +6 +a = 1 +b = 2 +c = 3 +``` + +You can also write parameters before `*args` to allow for specific positional arguments. +Individual keyword arguments then have to appear before `**kwargs`. + +```exercism/caution +[Arguments have to be structured][Positional and keyword arguments] like this: + +`def my_function(, *args, , **kwargs)` + +If you don't follow this order then you will get an error. +``` + +```python +>>> def my_function(a, b, *args): +... print(a) +... print(b) +... print(args) + +>>> my_function(1, 2, 3, 4, 5) +1 +2 +(3, 4, 5) +``` + +Writing arguments in an incorrect order will result in an error: + +```python +>>>def my_function(*args, a, b): +... print(args) + +>>>my_function(1, 2, 3, 4, 5) +Traceback (most recent call last): + File "c:\something.py", line 3, in + my_function(1, 2, 3, 4, 5) +TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b' +``` + +### Unpacking into function calls + +You can use `*` to unpack a `list`/`tuple` of arguments into a function call. +This is very useful for functions that don't accept an `iterable` or `iterator`: + +```python +>>> def my_function(a, b, c): +... print(c) +... print(b) +... print(a) + +numbers = [1, 2, 3] +>>> my_function(*numbers) +3 +2 +1 +``` + +Using `*` unpacking with the `zip()` function is another common use case. +Since `zip()` takes multiple iterables and returns a list of tuples with the values from each iterable grouped: + +```python +>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True]) +>>> a, *rest = zip(*values) +>>> rest +[('y', 2, False), ('z', 3, True)] +``` + +[multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ +[sorting algorithms]: https://realpython.com/sorting-algorithms-python/ +[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp +[deep unpacking]: https://mathspp.com/blog/pydonts/deep-unpacking#deep-unpacking +[packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ +[items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ +[args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ diff --git a/concepts/unpacking-and-multiple-assignment/introduction.md b/concepts/unpacking-and-multiple-assignment/introduction.md index fcde74642c..0bb09dee5c 100644 --- a/concepts/unpacking-and-multiple-assignment/introduction.md +++ b/concepts/unpacking-and-multiple-assignment/introduction.md @@ -1,2 +1,22 @@ -#TODO: Add introduction for this concept. +# Unpacking and Multiple Assignment +Unpacking refers to the act of extracting the elements of a collection, such as a `list`, `tuple`, or `dict`, using iteration. +Unpacked values can be assigned to variables within the same step. +With unpacking, there are some special operators used: `*` and `**`. + + +When unpacking a list or tuple, the `*` operator can be used to assign all the remaining elements to a variable. +When unpacking a dictionary, the `**` operator can be used to assign all the remaining key-value pairs to a variable. +When these operators are used without a collection, they _pack_ a number of values into a `list`, `tuple`, or `dict`. + +It is common in Python to also exploit this unpacking/packing behavior when defining functions that take an arbitrary number of positional or keyword arguments. +You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` + +[Multiple assignment][multiple assignment] is the ability to assign multiple variables in one line. +This allows for code to be more concise and readable, and is done by separating the variables with a comma. + +```exercism/caution +`*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. +``` + +[multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ diff --git a/concepts/unpacking-and-multiple-assignment/links.json b/concepts/unpacking-and-multiple-assignment/links.json index eb5fb7c38a..8caff2f9cd 100644 --- a/concepts/unpacking-and-multiple-assignment/links.json +++ b/concepts/unpacking-and-multiple-assignment/links.json @@ -1,18 +1,26 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/", + "description": "Trey Hunner: Astrisks in Python - What they are and How to Use Them." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/", + "description": "Trey Hunner: Tuple Unpacking Improves Python Code Readability." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://stackabuse.com/unpacking-in-python-beyond-parallel-assignment/", + "description": "Stack Abuse: Unpacking in Python Beyond Parallel Assignment." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://dbader.org/blog/python-nested-unpacking", + "description": "Dan Bader: Python Nested Unpacking." + }, + { + "url": "https://peps.python.org/pep-3132/", + "description": "Pep 3132: Extended Iterable Unpacking." + }, + { + "url": "https://peps.python.org/pep-0448/", + "description": "PEP 0448: Additional Unpacking Generalizations." } ] diff --git a/config.json b/config.json index bb349eede7..61cc1285f0 100644 --- a/config.json +++ b/config.json @@ -136,6 +136,14 @@ "prerequisites": ["loops", "lists", "tuples"], "status": "beta" }, + { + "slug": "locomotive-engineer", + "name": "Locomotive Engineer", + "uuid": "e1b8b9c9-21c3-47b1-b645-5938b3110c78", + "concepts": ["unpacking-and-multiple-assignment"], + "prerequisites": ["loops", "lists", "tuples", "dicts"], + "status": "wip" + }, { "slug": "cater-waiter", "name": "Cater Waiter", diff --git a/exercises/concept/locomotive-engineer/.docs/hints.md b/exercises/concept/locomotive-engineer/.docs/hints.md new file mode 100644 index 0000000000..929448f286 --- /dev/null +++ b/exercises/concept/locomotive-engineer/.docs/hints.md @@ -0,0 +1,35 @@ +# Hints + +## General + +- To extract multiple arguments in the function parameters so can you pack them with the `*args` operator for list or tuples or `kwargs` for keyword-based arguments. +- To pack or unpack use the `*` or `**` operator. + +## 1. Create a list of all wagons + +- Multiple arguments in the function parameters can be packed with the `*args` operator. + +## 2. Fix list of wagons + +- Using unpacking with the `*` operator, lets you extract the first two elements of a list while keeping the rest intact. +- To add another list into an existing list, you can use the `*` operator to "spread" the list. + +## 3. Add missing stops + +- Using `**kwargs` as a function parameter will allow an arbitrary amount of keyword arguments to be passed. +- Using `**(dict)` as an argument will unpack a dictionary into keyword arguments. +- You can put keyword arguments in a `{}` or `dict()`. +- To get the values out of a dictionary, you can use the `.values()` method. + +## 4. Extend routing information + +- Using `**(dict)` as an argument will unpack a dictionary into keyword arguments. +- You can put keyword arguments in a `{}` or `dict()`. + +## 5. Fix the wagon depot + +- `zip(*iterators)` can use used to transpose a nested list. +- To extract data from zipped iterators, you can use a for loop. +- you can also unpack zipped iterators using `*`. + `[*content] = zip(iterator_1, iterator_2)` will unzip the `tuple` produced by `zip()` into a `list`. + diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md new file mode 100644 index 0000000000..9b80b9ff32 --- /dev/null +++ b/exercises/concept/locomotive-engineer/.docs/instructions.md @@ -0,0 +1,110 @@ +# Instructions + +Your friend Linus is a Locomotive Engineer who drives cargo trains between cities. +Your friend is great at handling trains, although they aren't amazing at handling the logistics computers and would like your programming help organizing the train and correcting mistakes in the data. + +```exercism/note +This exercise could easily be solved using `list` slicing, indexing, and `dict` methods. +However, we'd like you to practice using Unpacking and Multiple Assignment to solve each of the tasks below. +``` + +## 1. Create a list of all wagons + +Your friend has been keeping track of each wagon identifier, but they're never sure how many wagons they are going to have to process at any given time. It would be much easier for the rest of the logistics program to have the data to be returned as a `list`. + +Implement a function `get_list_of_wagons` that accepts an arbitrary amount of positive integers which are the IDs of each wagon. +It should then return the given IDs as a `list`. + +```python +>>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5) +[1, 7, 12, 3, 14, 8, 3] +``` + +## 2. Fix the list of wagons + +At this point, you are starting to get a feel for your friend's data and how it's used in the logistics program. +The train ID system works by assigning the locomotive an ID of `1` and then assigning the remainder of the wagons a randomly chosen ID greater than `1`. + +But then your friend had to connect two new wagons to the train and forgot to update the system! +Now the first two wagons in the `list` have to be moved to the back of the train, or everything will be out of order. + +Additionally, your friend just found a second `list` that appears to contain missing wagon IDs, and would like you to merge it together with the main wagon ID `list`. +All they can remember is that once the new wagons are moved to the end, the values from the second list should be placed directly after the designated locomotive. + +Your friend would be really grateful to you for fixing their mistakes and consolidating the data. + +Implement a function `fix_list_of_wagons` that takes two `lists` containing wagon IDs as the arguments. +It should reposition the first two items of the first list to the end, and then insert the values from the second list behind the locomotive ID (`1`). +The function should then `return` the `list` with the modifications. + +```python +>>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15]) +[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5] +``` + +## 3. Add missing stops + +Now that all the wagon data is correct, your friend would like you to update the system's routing information. +During the journey, the train will stop at a few stations to pick up and/or drop off cargo. +Each journey will have a different amount of stops. To simplify setting up the routing program your friend would like you to add the missing stops to the routing `dict`. + +Implement a function `add_missing_stops` that accepts a routing `dict` followed by an arbitrary amount of additional keyword arguments. These arguments could be in the form of a `dict` which holds one or more stops in order, or a number of `stop_number=city` pairs. +The function should then return the routing `dict`, updated with a key that holds a list of all the stops in order. + +```python +>>> add_missing_stops({"from": "Berlin", "to": "Hamburg"}, {"stop_1": "Hamburg", "stop_2": "Hannover", "stop_3": "Frankfurt"}) +{"from": "Berlin", "to": "Hamburg", "stops": ["Hamburg", "Hannover", "Frankfurt"]} + +>>> add_missing_stops({"from": "New York", "to": "Miami"}, stop_1="Washington, DC", stop_2="Charlotte", stop_3="Atlanta", stop_4="Jacksonville", stop_5="Orlando") +{"from": "New York", "to": "Miami", "stops": ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]} +``` + +## 4. Extend routing information + +Your friend has been working on the routing program and has noticed that the routing information is missing some important details. +Initial routing information has been constructed as a `dict`, and your friend would like you to update it with the additions provided. +Every route requires slightly different information, so your friend would really prefer a generic solution. + +Implement a function `extend_route_information` that accepts two `dicts`. +The first `dict` contains which cities the train route moves between. + +The second `dict` contains other routing details such as train speed or length. +The function should return a consolidated `dict` with all routing information. + +```exercism/note +The second dict can contain different properties than the ones shown in the example. +``` + +```python +>>> extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"}) +{"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"} +``` + +## 5. Fix the wagon depot + +When your friend was surveying the wagon depot they noticed that the wagons were not getting stored in the correct order. +In addition to an ID, each wagon has a color that corresponds to the type of cargo it carries. +Wagons are stored in the depot in grids, with each column in the grid grouped by wagon color. + +In the control system, it appears that the lists of wagons to be stored in the depot have their _rows_ grouped by color. But for the storage grid to work correctly, each row has to have three different colors, so that the columns align properly. +Your friend would like you to help them sort out the wagon depot lists, so that the wagons get stored correctly. + +Implement a function `fix_wagon_depot` that accepts a nested `list`. +The first `list` contains the first row of wagons, the second `list` contains the second row of wagons and the third `list` contains the third row of wagons. All rows are of equal length. +Every wagon within a row is represented by a `tuple` with (``, ``). + +Your function should return a `list` with the three row `lists` reordered with the wagons swapped into their correct positions. + +```python +>>> fix_wagon_depot([ + [(2, "red"), (4, "red"),(8, "red")], + [(5, "blue"),(9, "blue"),(13,"blue")], + [(3, "orange"),(7, "orange"), (11, "orange")], + ]) + +[ +[(2, "red"),(5, "blue"),(3, "orange")], +[(4, "red"),(9, "blue"),(7, "orange")], +[(8, "red"),(13,"blue"),(11, "orange")] +] +``` diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md new file mode 100644 index 0000000000..779a6e0504 --- /dev/null +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -0,0 +1,289 @@ +# Unpacking and Multiple Assignment + +Unpacking refers to the act of extracting the elements of a collection, such as a `list`, `tuple`, or `dict`, using iteration. +Unpacked values can be assigned to variables within the same step. +With unpacking, there are some special operators used: `*` and `**`. +When unpacking a list or tuple, the `*` operator can be used to assign all the remaining elements to a variable. +When unpacking a dictionary, the `**` operator can be used to assign all the remaining key-value pairs to a variable. + +When these operators are used without a collection they will _pack_ a number of values into a `list`, `tuple`, or dictionary. +It is common to use this kind of behavior when creating functions that take an arbitrary number of arguments. + +Multiple assignment is the ability to assign multiple variables in one line. +This is done by separating the variables with a comma. + +```exercism/caution +`*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. +``` + +## Multiple assignment + +[Multiple assignment][multiple assignment] is the ability to assign multiple variables in one line. +This allows for code to be more concise and readable. +There has to be x number of variables on the left side of the `=` sign and x number of values on the right side of the `=` sign. +To separate the values, use a comma `,`: + +```python +>>> a, b = 1, 2 +>>> a +1 +``` + +Multiple assignment is not limited to one data type but can instead be used with any data type. +For example: + +```python +>>> a, b, c = 1, "Hello", True +>>> a +1 + +>>> b +'Hello' + +>>> c +True +``` + +## Unpacking + +```exercism/note +The examples below use lists but the same concepts apply to tuples. +``` + +In Python, it is possible to [unpack a `list`/`tuple`/`dictionary`][unpacking] into distinct variables. +Since values appear within lists in a specific order, it is therefore possible to _unpack_ a `list` into variables in the same order. + +Unpacking a list into variables: + +```python +>>> fruits = ["apple", "banana", "cherry"] +>>> x, y, z = fruits +>>> x +"apple" +``` + +If there are values that are not needed then you can use `_` to ignore those values: + +```python +>>> fruits = ["apple", "banana", "cherry"] +>>> _, _, z = fruits +>>> z +"cherry" +``` + +You can also do [deep unpacking][deep unpacking] on a `list`, which assigns values from a `list` within a `list` (_this is also known as nested list unpacking_): + +```python +>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] +>>> [[a, b], [c, d]] = fruits_vegetables +>>> a +"apple" + +>>> d +"potato" +``` + +Deep unpacking and normal unpacking can be mixed together: + +```python +>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] +>>> [a, [c, d]] = fruits_vegetables +>>> a +["apple", "banana"] + +>>> c +"carrot" +``` + +If the unpacking has variables with incorrect placement and/or an incorrect number of values, you will get a `ValueError`: + +```python +>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] +>>> [[a, b], [d]] = fruits_vegetables + +ValueError: too many values to unpack (expected 1) +``` + +### Unpacking a list/tuple with `*` + +When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the remainder values. +This can be used instead of slicing the `list`/`tuple`, which in some situations could be more readable. +For example, we can extract the first element below and then pack the remaining values into a new `list` without the first element: + +```python +>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] +>>> x, *last = fruits +>>> x +"apple" + +>>> last +["banana", "cherry", "orange", "kiwi", "melon", "mango"] +``` + +We can also extract the values at the beginning and end of the `list` while grouping all the values in the middle: + +```python +>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] +>>> x, *middle, y, z = fruits +>>> y +"melon" + +>>> middle +["banana", "cherry", "orange", "kiwi"] +``` + +### Unpacking a dictionary + +[Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`. +Iteration over dictionaries defaults to the `keys`. +So when unpacking a `dict`, you can only unpack the `keys` and not the `values`: +```python +>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} +>>> x, y, z = fruits_inventory +>>> x +"apple" +``` + +If you want to unpack the values then you can use the `values()` method: + +```python +>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} +>>> x, y, z = fruits_inventory.values() +>>> x +6 +``` + +## Packing + +As with unpacking, _packing_ uses the same `*` and `**` operators. +[Packing][packing and unpacking]] is the ability to group multiple values into one variable. +This is useful for when you want to _unpack_ values, make changes, and then _pack_ the results back into a variable. +It also makes it possible to perform merges on 2 or more `lists`/`tuples`/`dicts`. + +### Packing a list/tuple with `*` + +Packing a `list`/`tuple` is done by using the `*` operator +This will pack all the variables into a list/tuple. + +```python +>>> fruits = ["apple", "banana", "cherry"] +>>> more_fruits = ["orange", "kiwi", "melon", "mango"] +>>> combined_fruits_lists = [*fruits, *more_fruits] + +>>> combined_fruits_lists +["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] +``` + +### Packing a dictionary with `**` + +Packing a dictionary is done by using the `**` operator. +This will pack all the variables into a dictionary. + +```python +>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} +>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3} +>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory} + +>>> combined_fruits_inventory +{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3} +``` + +## Usage of `*` and `**` with a function + +### Packing with function parameters + +When you have a function that accepts an arbitrary or large number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] to pack or group those parameters together. +`*args` is used for packing/signaling an arbitrary number of positional (non-keyworded) arguments. +`**kwargs` is used for packing/signaling an arbitrary number of keyword arguments to a function. + +Usage of `*args`: + +```python +>>> def my_function(*args): +... print(args) + +>>> my_function(1, 2, 3) +(1, 2, 3) + +>>> my_function("Hello", "World") +("Hello", "World") + +>>> my_function(1, 2, 3, "Hello", "Mars") +(1, 2, 3, "Hello", "Mars") +``` + +Usage of `**kwargs`: + +```python +>>> def my_function(**kwargs): +... print(kwargs) + +>>> my_function(a=1, b=2, c=3) +{"a": 1, "b": 2, "c": 3} +``` + +```exercism/caution +[Arguments have to be structured][Positional and keyword arguments] like this: + +`def my_function(, *args, , **kwargs)` + +If you don't follow this order then you will get an error. +``` + +```python +>>> def my_function(a, b, *args): +... print(a) +... print(b) +... print(args) + +>>> my_function(1, 2, 3, 4, 5) +1 +2 +(3, 4, 5) +``` + +Writing arguments in an incorrect order will result in an error: + +```python +>>>def my_function(*args, a, b): +... print(args) + +>>>my_function(1, 2, 3, 4, 5) +Traceback (most recent call last): + File "c:\something.py", line 3, in + my_function(1, 2, 3, 4, 5) +TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b' +``` + +### Unpacking into function calls + +You can use `*` to unpack a `list`/`tuple` of arguments into a function call. +This is very useful for functions that don't accept an `iterable` or `iterator`: +```python +>>> def my_function(a, b, c): +... print(c) +... print(b) +... print(a) + +numbers = [1, 2, 3] +>>> my_function(*numbers) +3 +2 +1 +``` + +Using `*` unpacking with the `zip()` function is another common use case. +Since `zip()` takes multiple iterables and returns a list of tuples with the values from each iterable grouped: + +```python +>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True]) +>>> a, *rest = zip(*values) +>>> rest +[('y', 2, False), ('z', 3, True)] +``` + +[multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ +[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp +[deep unpacking]: https://mathspp.com/blog/pydonts/deep-unpacking#deep-unpacking +[packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ +[args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ diff --git a/exercises/concept/locomotive-engineer/.meta/config.json b/exercises/concept/locomotive-engineer/.meta/config.json new file mode 100644 index 0000000000..76ecea037c --- /dev/null +++ b/exercises/concept/locomotive-engineer/.meta/config.json @@ -0,0 +1,11 @@ +{ + "blurb": "Learn about unpacking and multiple assignment in Python while helping Linus with his train control system.", + "icon": "tracks-on-tracks-on-tracks", + "authors": ["meatball","BethanyG"], + "files": { + "solution": ["locomotive_engineer.py"], + "test": ["locomotive_engineer_test.py"], + "exemplar": [".meta/exemplar.py"] + }, + "forked_from": ["javascript/train-driver"] +} diff --git a/exercises/concept/locomotive-engineer/.meta/design.md b/exercises/concept/locomotive-engineer/.meta/design.md new file mode 100644 index 0000000000..af4109a9b0 --- /dev/null +++ b/exercises/concept/locomotive-engineer/.meta/design.md @@ -0,0 +1,69 @@ +# Design + +## TODO: Add introduction for this concept. +## Goal + +This concept exercise is meant to teach an understanding/use of `unpacking` and the `*` (splat) and `**` (double splat) operators in Python. + +
+ +## Learning objectives + +- Understand/use `unpacking` through the use of `*` and `**` _prefix_ operators in various scenarios + - `*` and `**` as _prefixes_ ..... not to be confused with `*` (_multiply_) and `**` (_exponentiation_) as _infix_, or mathematical operators (**consider a link in the links doc or a mention in dig deeper.**) + - ~~what happens in the process of "unpacking" - form, ordering, & iteration~~ (this will go in a **dig deeper** or the link docs.) + - use in arguments to `functions` + - use in argument _capture_ for `functions` (_aka passing an arbitrary number of arguments -- *args * & **kwargs_) + - ~~use in defining `keyword only arguments`~~ (_topic moved to arguments exercise_). + - use in iterable (_mainly `tuple` and `list`_) unpacking & packing + - use in `dict` unpacking & packing +- Understand/use `unpacking` via `multiple assignment` + - using `multiple assignment ` in place of `indexing` + - using `multiple assigment` + `*` in place of `slicing` + - ~~using "list-like" syntax & "tuple-like" syntax~~ + - unpacking plus "leftovers" via `*` +- Differences between straight `multiple assignment` and `*` & `**` +- Deep unpacking + + +## Concepts + +- `unpacking` +- `unpacking generalizations` +- `multiple assignment` + + +## Topics that are Out of scope + +- `classes` +- `comprehensions` +- `comprehensions` in `lambdas` +- `map()`, `filter()` or `functools.reduce()` in a `comprehension` +- `function-arguments` beyond explaining briefly how `*`, `**` work in function arguments. +- `functools` beyond `functools.reduce()`(_this will get its own exercise_) +- `generators` +- using an `assignment expression` or "walrus" operator (`:=`) alone or in a `lambda` + + +## Prerequisites + +- `basics` +- `bools` +- `comparisons` +- `dicts` +- `lists` +- `numbers` +- `strings` +- `tuples` + + +## Representer + +This exercise does not require any specific logic to be added to the [representer][representer] + +## Analyzer + +This exercise does not require any specific logic to be added to the [analyzer][analyzer]. + +[analyzer]: https://github.com/exercism/python-analyzer +[representer]: https://github.com/exercism/python-representer \ No newline at end of file diff --git a/exercises/concept/locomotive-engineer/.meta/exemplar.py b/exercises/concept/locomotive-engineer/.meta/exemplar.py new file mode 100644 index 0000000000..861cce2fde --- /dev/null +++ b/exercises/concept/locomotive-engineer/.meta/exemplar.py @@ -0,0 +1,48 @@ +"""Functions which helps the locomotive engineer to keep track of the train.""" + + +def get_list_of_wagons(*args): + return list(args) + + +def fix_list_of_wagons(each_wagons_id, missing_wagons): + """Fix the list of wagons. + + :param each_wagons_id: list - the list of wagons. + :param missing_wagons: list - the list of missing wagons. + :return: list - list of wagons. + """ + first, second, loctomotive, *rest = each_wagons_id + return [loctomotive, *missing_wagons, *rest, first, second] + + +def add_missing_stops(route, **kwargs): + """Add missing stops to route dict. + + :param route: dict - the dict of routing information. + :param **kwards: arbitrary number of stops. + :return: dict - updated route dictionary. + """ + return {**route, "stops": list(kwargs.values())} + + +def extend_route_information(route, more_route_information): + """Extend the route information with the more_route_information. + + :param route: dict - the route information. + :param more_route_information: dict - extra route information. + :return: dict - extended route information. + """ + return {**route, **more_route_information} + + +def fix_wagon_depot(wagons_rows): + """Fix the list of rows of wagons. + + :param wagons_rows: list[tuple] - the list of rows of wagons. + :return: list[tuple] - list of rows of wagons. + """ + + [*row_one], [*row_two], [*row_three] = zip(*wagons_rows) + + return [row_one, row_two, row_three] diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer.py b/exercises/concept/locomotive-engineer/locomotive_engineer.py new file mode 100644 index 0000000000..6809958b3f --- /dev/null +++ b/exercises/concept/locomotive-engineer/locomotive_engineer.py @@ -0,0 +1,38 @@ +"""Functions which helps the locomotive engineer to keep track of the train.""" + +# TODO: define the 'get_list_of_wagons' function + + +# TODO: define the 'fixListOfWagons()' function +def fix_list_of_wagons(each_wagons_id, missing_wagons): + """Fix the list of wagons. + + :parm each_wagons_id: list - the list of wagons. + :parm missing_wagons: list - the list of missing wagons. + :return: list - list of wagons. + """ + pass + + +# TODO: define the 'add_missing_stops()' function + + +# TODO: define the 'extend_route_information()' function +def extend_route_information(route, more_route_information): + """Extend the route information with the more_route_information. + + :param route: dict - the route information. + :param more_route_information: dict - extra route information. + :return: dict - extended route information. + """ + pass + + +# TODO: define the 'fix_wagon_depot()' function +def fix_wagon_depot(wagons_rows): + """Fix the list of rows of wagons. + + :param wagons_rows: list[tuple] - the list of rows of wagons. + :return: list[tuple] - list of rows of wagons. + """ + pass diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py new file mode 100644 index 0000000000..537ac75b0e --- /dev/null +++ b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py @@ -0,0 +1,82 @@ +import unittest +import pytest +from locomotive_engineer import (get_list_of_wagons, + fix_list_of_wagons, + add_missing_stops, + extend_route_information, + fix_wagon_depot) + +class InventoryTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_get_list_of_wagons(self): + input_data = [(1,5,2,7,4), (1,5), (1,), (1,9,3), (1,10,6,3,9,8,4,14,24,7)] + output_data = [[1,5,2,7,4], [1,5], [1], [1,9,3], [1,10,6,3,9,8,4,14,24,7]] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + self.assertEqual(get_list_of_wagons(*input_data), output_data) + + @pytest.mark.task(taskno=2) + def test_fix_list_of_wagons(self): # One extra case needed at first + input_data = [([3, 27, 1, 14, 10, 4, 12, 6, 23, 17, 13, 22, 28, 19], [8, 10, 5, 9, 36, 7, 20]), + ([4, 2, 1], [8, 6, 15]), + ([3, 14, 1, 25, 7, 19, 10], [8, 6, 4, 5, 9, 21, 2, 13]) + ] + output_data = [[1, 8, 10, 5, 9, 36, 7, 20, 14, 10, 4, 12, 6, 23, 17, 13, 22, 28, 19, 3, 27], + [1, 8, 6, 15, 4, 2], + [1, 8, 6, 4, 5, 9, 21, 2, 13, 25, 7, 19, 10, 3, 14] + ] + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + self.assertEqual(fix_list_of_wagons(input_data[0], input_data[1]), output_data) + + @pytest.mark.task(taskno=3) + def test_add_missing_stops(self): + input_data = (({"from": "Berlin", "to": "Hamburg"}, {"stop_1": "Lepzig", "stop_2": "Hannover", "stop_3": "Frankfurt"}), + ({"from": "Paris", "to": "London"}, {"stop_1": "Lille"}), + ({"from": "New York", "to": "Philadelphia"},{}), + ({"from": "Gothenburg", "to": "Copenhagen"}, {"stop_1": "Kungsbacka", "stop_2": "Varberg", "stop_3": "Halmstad", "stop_4": "Angelholm", "stop_5": "Lund", "stop_6": "Malmo"}) + ) + output_data = [{"from": "Berlin", "to": "Hamburg", "stops": ["Lepzig", "Hannover", "Frankfurt"]}, + {"from": "Paris", "to": "London", "stops": ["Lille"]}, + {"from": "New York", "to": "Philadelphia", "stops": []}, + {"from": "Gothenburg", "to": "Copenhagen", "stops": ["Kungsbacka", "Varberg", "Halmstad", "Angelholm", "Lund", "Malmo"]} + ] + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + self.assertEqual(add_missing_stops(input_data[0], **input_data[1]), output_data) + + @pytest.mark.task(taskno=4) + def test_extend_route_information(self): + input_data = [({"from": "Berlin", "to": "Hamburg"}, {"timeOfArrival": "12:00", "precipitation": "10", "temperature": "5", "caboose": "yes"}), + ({"from": "Paris", "to": "London"}, {"timeOfArrival": "10:30", "temperature": "20", "length": 15}), + ({"from": "Gothenburg", "to": "Copenhagen"}, {"precipitation": "1", "timeOfArrival": "21:20", "temperature": "-6"})] + output_data = [{"from": "Berlin", "to": "Hamburg", "timeOfArrival": "12:00", "precipitation": "10", "temperature": "5", "caboose": "yes"}, + {"from": "Paris", "to": "London", "timeOfArrival": "10:30", "temperature": "20", "length": 15}, + {"from": "Gothenburg", "to": "Copenhagen", "precipitation": "1", "timeOfArrival": "21:20", "temperature": "-6"} + ] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + self.assertEqual(extend_route_information(input_data[0], input_data[1]), output_data) + + @pytest.mark.task(taskno=5) + def test_fix_wagon_depot(self): + input_data = ( + [[(2, "red"), (4, "red"), (8, "red")], [(5, "blue"), (9, "blue"), (13, "blue")], [(3, "orange"), (7, "orange"), (11, "orange")]], + [[(6, "blue"), (10, "blue"), (14, "blue")], [(7, "red"), (4, "red"), (2, "red")], [(3, "orange"), (11, "orange"), (15, "orange")]], + [[(7, "pink"), (4, "pink"), (2, "pink")],[(10, "green"), (6, "green"), (14, "green")], [(9, "yellow"), (5, "yellow"), (13, "yellow")]], + [[(3, "purple"), (11, "purple"), (15, "purple")], [(20, "black"), (16, "black"), (12, "black")], [(19, "white"), (17, "white"), (18, "white")]] + ) + + output_data = ( + [[(2, "red"), (5, "blue"), (3, "orange")],[(4, "red"), (9, "blue"), (7, "orange")], [(8, "red"), (13, "blue"), (11, "orange")]], + [[(6, "blue"), (7, "red"), (3, "orange")],[(10, "blue"), (4, "red"), (11, "orange")], [(14, "blue"), (2, "red"), (15, "orange")]], + [[(7, "pink"), (10, "green"), (9, "yellow")], [(4, "pink"), (6, "green"), (5, "yellow")], [(2, "pink"), (14, "green"), (13, "yellow")]], + [[(3, "purple"), (20, "black"), (19, "white")], [(11, "purple"), (16, "black"), (17, "white")], [(15, "purple"), (12, "black"), (18, "white")]] + ) + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + self.assertEqual(fix_wagon_depot(input_data), output_data) \ No newline at end of file From 5b3b0aaa7555a9aead9e5e2f8e9a01547e99e3da Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Thu, 24 Nov 2022 08:40:54 +0100 Subject: [PATCH 041/826] add author --- concepts/unpacking-and-multiple-assignment/.meta/config.json | 2 +- exercises/concept/locomotive-engineer/.meta/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/.meta/config.json b/concepts/unpacking-and-multiple-assignment/.meta/config.json index ecf013c7ac..f984160fe3 100644 --- a/concepts/unpacking-and-multiple-assignment/.meta/config.json +++ b/concepts/unpacking-and-multiple-assignment/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "Unpacking is the process of extracting individual elements of a collection, such as a list, tuple, or dictionary by iterating over them. Unpacked values can be assigned to variables within the same step. Multiple assignment is the ability to assign values to multiple variables in one line.", - "authors": ["meatball","bethanyg"], + "authors": ["meatball133","bethanyg"], "contributors": [] } diff --git a/exercises/concept/locomotive-engineer/.meta/config.json b/exercises/concept/locomotive-engineer/.meta/config.json index 76ecea037c..86a8a8b6a3 100644 --- a/exercises/concept/locomotive-engineer/.meta/config.json +++ b/exercises/concept/locomotive-engineer/.meta/config.json @@ -1,7 +1,7 @@ { "blurb": "Learn about unpacking and multiple assignment in Python while helping Linus with his train control system.", "icon": "tracks-on-tracks-on-tracks", - "authors": ["meatball","BethanyG"], + "authors": ["meatball133","BethanyG"], "files": { "solution": ["locomotive_engineer.py"], "test": ["locomotive_engineer_test.py"], From 92f2550c7f4401f532484f924a480fa867a60932 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Nov 2022 23:01:11 +0000 Subject: [PATCH 042/826] Bump exercism/pr-commenter-action from 1.3.0 to 1.3.1 Bumps [exercism/pr-commenter-action](https://github.com/exercism/pr-commenter-action) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/exercism/pr-commenter-action/releases) - [Changelog](https://github.com/exercism/pr-commenter-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/exercism/pr-commenter-action/compare/v1.3.0...v1.3.1) --- updated-dependencies: - dependency-name: exercism/pr-commenter-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/pr-commenter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index 8b89511127..12591d4900 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -6,7 +6,7 @@ jobs: pr-comment: runs-on: ubuntu-latest steps: - - uses: exercism/pr-commenter-action@v1.3.0 + - uses: exercism/pr-commenter-action@v1.3.1 with: github-token: "${{ github.token }}" config-file: ".github/pr-commenter.yml" \ No newline at end of file From 02abfca1e2f49323f06e38693a3af3de1d69aa5d Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Fri, 25 Nov 2022 16:20:52 +0100 Subject: [PATCH 043/826] Create autoresponder for pausing community contributions We're going to take a step back and redesign the volunteering model for Exercism. Please see [this forum post](https://forum.exercism.org/t/freeing-our-maintainers-exercism-wide-changes-to-track-repositories/1109) for context. This PR adds an autoresponder that runs when an issue or PR is opened. If the person opening the issue is not a member of the Exercism organization, the autoresponder posts a comment and closes the issue. In the comment the author is directed to discuss the issue in the forum. If the discussion in the forum results in the issue/PR being approved, a maintainer or staff member will reopen it. Please feel free to merge this PR. It will be merged on December 1st, 2022. Please do not close it. If you wish to discuss this, please do so in [the forum post](https://forum.exercism.org/t/freeing-our-maintainers-exercism-wide-changes-to-track-repositories/1109) rather than here. --- .../pause-community-contributions.yml | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/pause-community-contributions.yml diff --git a/.github/workflows/pause-community-contributions.yml b/.github/workflows/pause-community-contributions.yml new file mode 100644 index 0000000000..85d18d1c5d --- /dev/null +++ b/.github/workflows/pause-community-contributions.yml @@ -0,0 +1,59 @@ +name: Pause Community Contributions + +on: + issues: + types: + - opened + pull_request_target: + types: + - opened + paths-ignore: + - "exercises/*/*/.approaches/**" + - "exercises/*/*/.articles/**" + workflow_dispatch: + +jobs: + pause: + name: Pause Community Contributions + runs-on: ubuntu-22.04 + steps: + - name: Detect if user is org member + uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 + id: is-organization-member + with: + script: | + if (context.actor == 'dependabot' || context.actor == 'exercism-bot' || context.actor == 'github-actions[bot]') { + return true; + } + + return github.rest.orgs.checkMembershipForUser({ + org: context.repo.owner, + username: context.actor, + }).then(response => response.status == 204) + .catch(err => true); + - name: Comment + if: steps.is-organization-member.outputs.result == 'false' + uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 + with: + script: | + const isIssue = !!context.payload.issue; + const subject = context.payload.issue || context.payload.pull_request; + const thing = (isIssue ? 'issue' : 'PR'); + const aThing = (isIssue ? 'an issue' : 'a PR'); + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Hello. Thanks for opening ${aThing} on Exercism. We are currently in a phase of our journey where we have paused community contributions to allow us to take a breather and redesign our community model. You can learn more in [this blog post](https://exercism.org/blog/freeing-our-maintainers). **As such, all issues and PRs in this repository are being automatically closed.**\n\nThat doesn’t mean we’re not interested in your ideas, or that if you’re stuck on something we don’t want to help. The best place to discuss things is with our community on the Exercism Community Forum. You can use [this link](https://forum.exercism.org/new-topic?title=${encodeURI(subject.title)}&body=${encodeURI(subject.body)}&category=python) to copy this into a new topic there.\n\n---\n\n_Note: If this ${thing} has been pre-approved, please link back to this ${thing} on the forum thread and a maintainer or staff member will reopen it._\n` + }) + - name: Close + if: steps.is-organization-member.outputs.result == 'false' + uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 + with: + script: | + github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + state: "closed", + }) From 676ec9742b5fde160b3c60d9cc77f6cea6bcfd6a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 26 Nov 2022 02:20:10 -0800 Subject: [PATCH 044/826] First Pass on Feedback Edits Instructions re-worked Added missing Link Started about.md re-write. --- .../about.md | 11 +-- .../locomotive-engineer/.docs/instructions.md | 76 ++++++++++--------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 68c8e0662b..385139d014 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -346,10 +346,11 @@ Since `zip()` takes multiple iterables and returns a list of tuples with the val [('y', 2, False), ('z', 3, True)] ``` -[multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ -[sorting algorithms]: https://realpython.com/sorting-algorithms-python/ -[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp +[args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ [deep unpacking]: https://mathspp.com/blog/pydonts/deep-unpacking#deep-unpacking -[packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ [items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ -[args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ +[multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ +[packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ +[positional and keyword arguments]: https://www.python-engineer.com/courses/advancedpython/18-function-arguments/ +[sorting algorithms]: https://realpython.com/sorting-algorithms-python/ +[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp \ No newline at end of file diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md index 9b80b9ff32..6da50371ff 100644 --- a/exercises/concept/locomotive-engineer/.docs/instructions.md +++ b/exercises/concept/locomotive-engineer/.docs/instructions.md @@ -1,19 +1,21 @@ # Instructions Your friend Linus is a Locomotive Engineer who drives cargo trains between cities. -Your friend is great at handling trains, although they aren't amazing at handling the logistics computers and would like your programming help organizing the train and correcting mistakes in the data. +Although they are amazing at handling trains, they are not amazing at handling logistics or computers. +They would like to enlist your programming help organiing train details and correcting mistakes in route data. ```exercism/note -This exercise could easily be solved using `list` slicing, indexing, and `dict` methods. -However, we'd like you to practice using Unpacking and Multiple Assignment to solve each of the tasks below. +This exercise could easily be solved using `slicing`, `indexing`, and various `dict` methods. +However, we would like you to practice packing, unpacking, and multiple assignment in solving each of the tasks below. ``` ## 1. Create a list of all wagons -Your friend has been keeping track of each wagon identifier, but they're never sure how many wagons they are going to have to process at any given time. It would be much easier for the rest of the logistics program to have the data to be returned as a `list`. +Your friend has been keeping track of each wagon identifier (ID), but they are never sure how many wagons the system is going to have to process at any given time. It would be much easier for the rest of the logistics program to have this data be packaged into a unified `list`. -Implement a function `get_list_of_wagons` that accepts an arbitrary amount of positive integers which are the IDs of each wagon. -It should then return the given IDs as a `list`. +Implement a function `get_list_of_wagons` that accepts an arbitrary number of wagon IDs. +Each ID will be a positive integer. +The function should then `return` the given IDs as a single `list`. ```python >>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5) @@ -22,20 +24,20 @@ It should then return the given IDs as a `list`. ## 2. Fix the list of wagons -At this point, you are starting to get a feel for your friend's data and how it's used in the logistics program. -The train ID system works by assigning the locomotive an ID of `1` and then assigning the remainder of the wagons a randomly chosen ID greater than `1`. +At this point, you are starting to get a feel for the data and how it's used in the logistics program. +The ID system always assigns the locomotive an ID of `1`, with the remainder of the wagons in the train assigned a randomly chosen ID greater than `1`. -But then your friend had to connect two new wagons to the train and forgot to update the system! -Now the first two wagons in the `list` have to be moved to the back of the train, or everything will be out of order. +Your friend had to connect two new wagons to the train and forgot to update the system! +Now, the first two wagons in the train `list` have to be moved to the end, or everything will be out of order. -Additionally, your friend just found a second `list` that appears to contain missing wagon IDs, and would like you to merge it together with the main wagon ID `list`. -All they can remember is that once the new wagons are moved to the end, the values from the second list should be placed directly after the designated locomotive. +To make matters more complicated, your friend just uncovered a second `list` that appears to contain missing wagon IDs. +All they can remember is that once the new wagons are moved, the IDs from this second list should be placed directly after the designated locomotive. -Your friend would be really grateful to you for fixing their mistakes and consolidating the data. +Linus would be really grateful to you for fixing their mistakes and consolidating the data. -Implement a function `fix_list_of_wagons` that takes two `lists` containing wagon IDs as the arguments. -It should reposition the first two items of the first list to the end, and then insert the values from the second list behind the locomotive ID (`1`). -The function should then `return` the `list` with the modifications. +Implement a function `fix_list_of_wagons` that takes two `lists` containing wagon IDs. +It should reposition the first two items of the first `list` to the end, and insert the values from the second `list` behind (_on the right hand side of_) the locomotive ID (`1`). +The function should then `return` a `list` with the modifications. ```python >>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15]) @@ -44,12 +46,14 @@ The function should then `return` the `list` with the modifications. ## 3. Add missing stops -Now that all the wagon data is correct, your friend would like you to update the system's routing information. -During the journey, the train will stop at a few stations to pick up and/or drop off cargo. -Each journey will have a different amount of stops. To simplify setting up the routing program your friend would like you to add the missing stops to the routing `dict`. +Now that all the wagon data is correct, Linus would like you to update the system's routing information. +Along a transport route, a train might make stops at a few different stations to pick up and/or drop off cargo. +Each journey could have a different amount of these intermediary delivery points. +Your friend would like you to update the systems routing `dict` with any missing/additional delivery information. -Implement a function `add_missing_stops` that accepts a routing `dict` followed by an arbitrary amount of additional keyword arguments. These arguments could be in the form of a `dict` which holds one or more stops in order, or a number of `stop_number=city` pairs. -The function should then return the routing `dict`, updated with a key that holds a list of all the stops in order. +Implement a function `add_missing_stops` that accepts a routing `dict` followed by a variable number of keyword arguments. +These arguments could be in the form of a `dict` holding one or more stops, or amy number of `stop_number=city` keyword pairs. +Your function should then return the routing `dict` updated with an additional `key` that holds a `list` of all the added stops in order. ```python >>> add_missing_stops({"from": "Berlin", "to": "Hamburg"}, {"stop_1": "Hamburg", "stop_2": "Hannover", "stop_3": "Frankfurt"}) @@ -61,18 +65,18 @@ The function should then return the routing `dict`, updated with a key that hold ## 4. Extend routing information -Your friend has been working on the routing program and has noticed that the routing information is missing some important details. -Initial routing information has been constructed as a `dict`, and your friend would like you to update it with the additions provided. -Every route requires slightly different information, so your friend would really prefer a generic solution. +Linus has been working on the routing program and has noticed that certain routes are missing some important details. +Initial route information has been constructed as a `dict` and your friend would like you to update that `dict` with whatever might be missing. +Every route in the system requires slightly different details, so Linus would really prefer a generic/flexable solution. -Implement a function `extend_route_information` that accepts two `dicts`. -The first `dict` contains which cities the train route moves between. +Implement a function called `extend_route_information` that accepts two `dicts`. +The first `dict` contains the origin and destination cities the train route runs between. -The second `dict` contains other routing details such as train speed or length. +The second `dict` contains other routing details such as train speed, length, or temprature. The function should return a consolidated `dict` with all routing information. ```exercism/note -The second dict can contain different properties than the ones shown in the example. +The second dict can contain different/more properties than the ones shown in the example. ``` ```python @@ -82,16 +86,18 @@ The second dict can contain different properties than the ones shown in the exam ## 5. Fix the wagon depot -When your friend was surveying the wagon depot they noticed that the wagons were not getting stored in the correct order. +When your Linus was surveying the wagon depot they noticed that the wagons were not getting stored in the correct order. In addition to an ID, each wagon has a color that corresponds to the type of cargo it carries. -Wagons are stored in the depot in grids, with each column in the grid grouped by wagon color. +Wagons are stored in the depot in grids, where each column in the grid has wagons of the same color. -In the control system, it appears that the lists of wagons to be stored in the depot have their _rows_ grouped by color. But for the storage grid to work correctly, each row has to have three different colors, so that the columns align properly. -Your friend would like you to help them sort out the wagon depot lists, so that the wagons get stored correctly. +However, the logistics system shows `lists` of wagons to be stored in the depot have their _rows_ grouped by color. +But for the storage grid to work correctly, each _row_ should have three different colors so that the _columns_ align by color. +Your friend would like you to sort out the wagon depot `lists`, so that the wagons get stored correctly. -Implement a function `fix_wagon_depot` that accepts a nested `list`. -The first `list` contains the first row of wagons, the second `list` contains the second row of wagons and the third `list` contains the third row of wagons. All rows are of equal length. -Every wagon within a row is represented by a `tuple` with (``, ``). +Implement a function called `fix_wagon_depot` that accepts a nested `list`. +The first `list` contains the first row of wagons, the second `list` contains the second row of wagons and the third `list` contains the third row of wagons. +All rows are of equal length. +Every wagon within a row is represented by a `tuple` with `(, )`. Your function should return a `list` with the three row `lists` reordered with the wagons swapped into their correct positions. From 6e28e160e4b439ed8f8c6395d3a94ebb35a29166 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 26 Nov 2022 22:57:29 -0800 Subject: [PATCH 045/826] Added Test Error Messages --- .../locomotive_engineer_test.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py index 537ac75b0e..d90198ac47 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py @@ -15,7 +15,8 @@ def test_get_list_of_wagons(self): for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertEqual(get_list_of_wagons(*input_data), output_data) + error_msg=f'Expected: {output_data} but got a different wagon list instead.' + self.assertEqual(get_list_of_wagons(*input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=2) def test_fix_list_of_wagons(self): # One extra case needed at first @@ -29,7 +30,8 @@ def test_fix_list_of_wagons(self): # One extra case needed at first ] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertEqual(fix_list_of_wagons(input_data[0], input_data[1]), output_data) + error_msg=f'Expected: {output_data} but got a different wagon list instead.' + self.assertEqual(fix_list_of_wagons(input_data[0], input_data[1]), output_data, msg=error_msg) @pytest.mark.task(taskno=3) def test_add_missing_stops(self): @@ -45,7 +47,8 @@ def test_add_missing_stops(self): ] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertEqual(add_missing_stops(input_data[0], **input_data[1]), output_data) + error_msg=f'Expected: {output_data} but got a different set of stops instead.' + self.assertEqual(add_missing_stops(input_data[0], **input_data[1]), output_data, msg=error_msg) @pytest.mark.task(taskno=4) def test_extend_route_information(self): @@ -59,7 +62,8 @@ def test_extend_route_information(self): for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertEqual(extend_route_information(input_data[0], input_data[1]), output_data) + error_msg=f'Expected: {output_data} but got a different route dictionary instead.' + self.assertEqual(extend_route_information(input_data[0], input_data[1]), output_data, msg=error_msg) @pytest.mark.task(taskno=5) def test_fix_wagon_depot(self): @@ -79,4 +83,5 @@ def test_fix_wagon_depot(self): for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertEqual(fix_wagon_depot(input_data), output_data) \ No newline at end of file + error_msg=f'Expected: {output_data} but got a different wagon depot list instead.' + self.assertEqual(fix_wagon_depot(input_data), output_data, msg=error_msg) \ No newline at end of file From 5428a33a1f7ddd9a5e3c6798fc22f08a4b221be4 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 27 Nov 2022 15:35:14 +0100 Subject: [PATCH 046/826] Added Matthijs as contributor and varius fixes --- .../unpacking-and-multiple-assignment/.meta/config.json | 2 +- concepts/unpacking-and-multiple-assignment/about.md | 6 +++--- exercises/concept/locomotive-engineer/.docs/instructions.md | 6 +++--- exercises/concept/locomotive-engineer/.meta/exemplar.py | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/.meta/config.json b/concepts/unpacking-and-multiple-assignment/.meta/config.json index f984160fe3..36aa817b97 100644 --- a/concepts/unpacking-and-multiple-assignment/.meta/config.json +++ b/concepts/unpacking-and-multiple-assignment/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "Unpacking is the process of extracting individual elements of a collection, such as a list, tuple, or dictionary by iterating over them. Unpacked values can be assigned to variables within the same step. Multiple assignment is the ability to assign values to multiple variables in one line.", "authors": ["meatball133","bethanyg"], - "contributors": [] + "contributors": ["MatthijsBlom"] } diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 385139d014..d87d614351 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -44,8 +44,8 @@ For example: True ``` -Multiple assignment also allows for the swapping of variables in `lists`. -This practice is pretty common in [linear sorting algorithms][sorting algorithms]. +Multiple assignment also allows for the swapping of elements in `lists`. +This practice is pretty common in [sorting algorithms][sorting algorithms]. For example: ```python @@ -353,4 +353,4 @@ Since `zip()` takes multiple iterables and returns a list of tuples with the val [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ [positional and keyword arguments]: https://www.python-engineer.com/courses/advancedpython/18-function-arguments/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ -[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp \ No newline at end of file +[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md index 6da50371ff..39f9c8253c 100644 --- a/exercises/concept/locomotive-engineer/.docs/instructions.md +++ b/exercises/concept/locomotive-engineer/.docs/instructions.md @@ -11,7 +11,7 @@ However, we would like you to practice packing, unpacking, and multiple assignme ## 1. Create a list of all wagons -Your friend has been keeping track of each wagon identifier (ID), but they are never sure how many wagons the system is going to have to process at any given time. It would be much easier for the rest of the logistics program to have this data be packaged into a unified `list`. +Your friend has been keeping track of each wagon identifier (ID), but they are never sure how many wagons the system is going to have to process at any given time. It would be much easier for the rest of the logistics program to have this data be packaged into a unified `list`. Implement a function `get_list_of_wagons` that accepts an arbitrary number of wagon IDs. Each ID will be a positive integer. @@ -86,7 +86,7 @@ The second dict can contain different/more properties than the ones shown in the ## 5. Fix the wagon depot -When your Linus was surveying the wagon depot they noticed that the wagons were not getting stored in the correct order. +When Linus was surveying the wagon depot they noticed that the wagons were not getting stored in the correct order. In addition to an ID, each wagon has a color that corresponds to the type of cargo it carries. Wagons are stored in the depot in grids, where each column in the grid has wagons of the same color. @@ -95,7 +95,7 @@ But for the storage grid to work correctly, each _row_ should have three differe Your friend would like you to sort out the wagon depot `lists`, so that the wagons get stored correctly. Implement a function called `fix_wagon_depot` that accepts a nested `list`. -The first `list` contains the first row of wagons, the second `list` contains the second row of wagons and the third `list` contains the third row of wagons. +The first `list` contains the first row of wagons, the second `list` contains the second row of wagons and the third `list` contains the third row of wagons. All rows are of equal length. Every wagon within a row is represented by a `tuple` with `(, )`. diff --git a/exercises/concept/locomotive-engineer/.meta/exemplar.py b/exercises/concept/locomotive-engineer/.meta/exemplar.py index 861cce2fde..6fdc680ab0 100644 --- a/exercises/concept/locomotive-engineer/.meta/exemplar.py +++ b/exercises/concept/locomotive-engineer/.meta/exemplar.py @@ -12,15 +12,15 @@ def fix_list_of_wagons(each_wagons_id, missing_wagons): :param missing_wagons: list - the list of missing wagons. :return: list - list of wagons. """ - first, second, loctomotive, *rest = each_wagons_id - return [loctomotive, *missing_wagons, *rest, first, second] + first, second, locomotive, *rest = each_wagons_id + return [locomotive, *missing_wagons, *rest, first, second] def add_missing_stops(route, **kwargs): """Add missing stops to route dict. :param route: dict - the dict of routing information. - :param **kwards: arbitrary number of stops. + :param **kwargs: arbitrary number of stops. :return: dict - updated route dictionary. """ return {**route, "stops": list(kwargs.values())} From 605b6f5eb35c958855c87cbf7617d28d7ab6da66 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 27 Nov 2022 15:43:12 +0100 Subject: [PATCH 047/826] Update about.md --- concepts/unpacking-and-multiple-assignment/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index d87d614351..7bd34e7eda 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -189,7 +189,7 @@ If you want to unpack the values then you can use the `values()` method: If both `keys` and `values` are needed, use the `items()` method. Using `items()` will generate tuples with `key-value` pairs. -This is because [`dict.items()` generates a `tuple`][items] and within it there is a `tuple` for each `key-value` pair:. +This is because [`dict.items()` generates an iterable with key-value `tuples`][items] and within it there is a `tuple` for each `key-value` pair:. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} From 2b4aac7aaa2c94bd2d3dc4bcb4cf8e40a748ad20 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:39:52 +0100 Subject: [PATCH 048/826] fix --- concepts/unpacking-and-multiple-assignment/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 7bd34e7eda..6212e4d944 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -189,7 +189,7 @@ If you want to unpack the values then you can use the `values()` method: If both `keys` and `values` are needed, use the `items()` method. Using `items()` will generate tuples with `key-value` pairs. -This is because [`dict.items()` generates an iterable with key-value `tuples`][items] and within it there is a `tuple` for each `key-value` pair:. +This is because [`dict.items()` generates an iterable with key-value `tuples`][items]. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} From 5fa0ed91e5887d52c93305104a511af57e851696 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 27 Nov 2022 20:47:00 +0100 Subject: [PATCH 049/826] Add missing functions & fixes --- .../locomotive-engineer/.docs/instructions.md | 43 +++++++++---------- .../locomotive-engineer/.meta/design.md | 21 +++------ .../locomotive-engineer/.meta/exemplar.py | 16 +++++-- .../locomotive_engineer.py | 18 +++++++- .../locomotive_engineer_test.py | 10 +++-- 5 files changed, 64 insertions(+), 44 deletions(-) diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md index 39f9c8253c..c81cedcff7 100644 --- a/exercises/concept/locomotive-engineer/.docs/instructions.md +++ b/exercises/concept/locomotive-engineer/.docs/instructions.md @@ -2,18 +2,18 @@ Your friend Linus is a Locomotive Engineer who drives cargo trains between cities. Although they are amazing at handling trains, they are not amazing at handling logistics or computers. -They would like to enlist your programming help organiing train details and correcting mistakes in route data. +They would like to enlist your programming help organizing train details and correcting mistakes in route data. ```exercism/note -This exercise could easily be solved using `slicing`, `indexing`, and various `dict` methods. +This exercise could easily be solved using slicing, indexing, and various `dict` methods. However, we would like you to practice packing, unpacking, and multiple assignment in solving each of the tasks below. ``` ## 1. Create a list of all wagons -Your friend has been keeping track of each wagon identifier (ID), but they are never sure how many wagons the system is going to have to process at any given time. It would be much easier for the rest of the logistics program to have this data be packaged into a unified `list`. +Your friend has been keeping track of each wagon identifier (ID), but they are never sure how many wagons the system is going to have to process at any given time. It would be much easier for the rest of the logistics program to have this data packaged into a unified `list`. -Implement a function `get_list_of_wagons` that accepts an arbitrary number of wagon IDs. +Implement a function `get_list_of_wagons()` that accepts an arbitrary number of wagon IDs. Each ID will be a positive integer. The function should then `return` the given IDs as a single `list`. @@ -25,18 +25,18 @@ The function should then `return` the given IDs as a single `list`. ## 2. Fix the list of wagons At this point, you are starting to get a feel for the data and how it's used in the logistics program. -The ID system always assigns the locomotive an ID of `1`, with the remainder of the wagons in the train assigned a randomly chosen ID greater than `1`. +The ID system always assigns the locomotive an ID of **1**, with the remainder of the wagons in the train assigned a randomly chosen ID greater than **1**. Your friend had to connect two new wagons to the train and forgot to update the system! Now, the first two wagons in the train `list` have to be moved to the end, or everything will be out of order. To make matters more complicated, your friend just uncovered a second `list` that appears to contain missing wagon IDs. -All they can remember is that once the new wagons are moved, the IDs from this second list should be placed directly after the designated locomotive. +All they can remember is that once the new wagons are moved, the IDs from this second `list` should be placed directly after the designated locomotive. Linus would be really grateful to you for fixing their mistakes and consolidating the data. -Implement a function `fix_list_of_wagons` that takes two `lists` containing wagon IDs. -It should reposition the first two items of the first `list` to the end, and insert the values from the second `list` behind (_on the right hand side of_) the locomotive ID (`1`). +Implement a function `fix_list_of_wagons()` that takes two `lists` containing wagon IDs. +It should reposition the first two items of the first `list` to the end, and insert the values from the second `list` behind (_on the right hand side of_) the locomotive ID (**1**). The function should then `return` a `list` with the modifications. ```python @@ -51,15 +51,15 @@ Along a transport route, a train might make stops at a few different stations to Each journey could have a different amount of these intermediary delivery points. Your friend would like you to update the systems routing `dict` with any missing/additional delivery information. -Implement a function `add_missing_stops` that accepts a routing `dict` followed by a variable number of keyword arguments. -These arguments could be in the form of a `dict` holding one or more stops, or amy number of `stop_number=city` keyword pairs. +Implement a function `add_missing_stops()` that accepts a routing `dict` followed by a variable number of keyword arguments. +These arguments could be in the form of a `dict` holding one or more stops, or any number of `stop_number=city` keyword pairs. Your function should then return the routing `dict` updated with an additional `key` that holds a `list` of all the added stops in order. ```python ->>> add_missing_stops({"from": "Berlin", "to": "Hamburg"}, {"stop_1": "Hamburg", "stop_2": "Hannover", "stop_3": "Frankfurt"}) -{"from": "Berlin", "to": "Hamburg", "stops": ["Hamburg", "Hannover", "Frankfurt"]} +>>> add_missing_stops({"from": "New York", "to": "Miami"}, + stop_1="Washington, DC", stop_2="Charlotte", stop_3="Atlanta", + stop_4="Jacksonville", stop_5="Orlando") ->>> add_missing_stops({"from": "New York", "to": "Miami"}, stop_1="Washington, DC", stop_2="Charlotte", stop_3="Atlanta", stop_4="Jacksonville", stop_5="Orlando") {"from": "New York", "to": "Miami", "stops": ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]} ``` @@ -67,16 +67,16 @@ Your function should then return the routing `dict` updated with an additional ` Linus has been working on the routing program and has noticed that certain routes are missing some important details. Initial route information has been constructed as a `dict` and your friend would like you to update that `dict` with whatever might be missing. -Every route in the system requires slightly different details, so Linus would really prefer a generic/flexable solution. +Every route in the system requires slightly different details, so Linus would really prefer a generic solution. -Implement a function called `extend_route_information` that accepts two `dicts`. +Implement a function called `extend_route_information()` that accepts two `dicts`. The first `dict` contains the origin and destination cities the train route runs between. -The second `dict` contains other routing details such as train speed, length, or temprature. +The second `dict` contains other routing details such as train speed, length, or temperature. The function should return a consolidated `dict` with all routing information. ```exercism/note -The second dict can contain different/more properties than the ones shown in the example. +The second `dict` can contain different/more properties than the ones shown in the example. ``` ```python @@ -94,12 +94,11 @@ However, the logistics system shows `lists` of wagons to be stored in the depot But for the storage grid to work correctly, each _row_ should have three different colors so that the _columns_ align by color. Your friend would like you to sort out the wagon depot `lists`, so that the wagons get stored correctly. -Implement a function called `fix_wagon_depot` that accepts a nested `list`. -The first `list` contains the first row of wagons, the second `list` contains the second row of wagons and the third `list` contains the third row of wagons. -All rows are of equal length. -Every wagon within a row is represented by a `tuple` with `(, )`. +Implement a function called `fix_wagon_depot()` that accepts a `list` of three items. +Each `list` item is a sublist (or "row") that contains three `tuples`. +Each `tuple` is a `(, )` pair. -Your function should return a `list` with the three row `lists` reordered with the wagons swapped into their correct positions. +Your function should return a `list` with the three "row" `lists` reordered to have the wagons swapped into their correct positions. ```python >>> fix_wagon_depot([ diff --git a/exercises/concept/locomotive-engineer/.meta/design.md b/exercises/concept/locomotive-engineer/.meta/design.md index af4109a9b0..933b7cb8ff 100644 --- a/exercises/concept/locomotive-engineer/.meta/design.md +++ b/exercises/concept/locomotive-engineer/.meta/design.md @@ -1,9 +1,8 @@ # Design -## TODO: Add introduction for this concept. ## Goal -This concept exercise is meant to teach an understanding/use of `unpacking` and the `*` (splat) and `**` (double splat) operators in Python. +This concept exercise is meant to teach an understanding/use of `unpacking` and the `*` (splat) and `**` (double splat) operators in Python.
@@ -11,40 +10,34 @@ This concept exercise is meant to teach an understanding/use of `unpacking` and - Understand/use `unpacking` through the use of `*` and `**` _prefix_ operators in various scenarios - `*` and `**` as _prefixes_ ..... not to be confused with `*` (_multiply_) and `**` (_exponentiation_) as _infix_, or mathematical operators (**consider a link in the links doc or a mention in dig deeper.**) - - ~~what happens in the process of "unpacking" - form, ordering, & iteration~~ (this will go in a **dig deeper** or the link docs.) - use in arguments to `functions` - - use in argument _capture_ for `functions` (_aka passing an arbitrary number of arguments -- *args * & **kwargs_) - - ~~use in defining `keyword only arguments`~~ (_topic moved to arguments exercise_). + - use in argument _capture_ for `functions` (_aka passing an arbitrary number of arguments -- *args * & \*\*kwargs_) - use in iterable (_mainly `tuple` and `list`_) unpacking & packing - use in `dict` unpacking & packing - Understand/use `unpacking` via `multiple assignment` - using `multiple assignment ` in place of `indexing` - - using `multiple assigment` + `*` in place of `slicing` - - ~~using "list-like" syntax & "tuple-like" syntax~~ + - using `multiple assignment` + `*` in place of `slicing` - unpacking plus "leftovers" via `*` -- Differences between straight `multiple assignment` and `*` & `**` +- Differences between straight `multiple assignment` and `*` & `**` - Deep unpacking - ## Concepts - `unpacking` - `unpacking generalizations` - `multiple assignment` - ## Topics that are Out of scope - `classes` - `comprehensions` - `comprehensions` in `lambdas` - `map()`, `filter()` or `functools.reduce()` in a `comprehension` -- `function-arguments` beyond explaining briefly how `*`, `**` work in function arguments. +- `function-arguments` beyond explaining briefly how `*`, `**` work in function arguments. - `functools` beyond `functools.reduce()`(_this will get its own exercise_) - `generators` - using an `assignment expression` or "walrus" operator (`:=`) alone or in a `lambda` - ## Prerequisites - `basics` @@ -55,7 +48,7 @@ This concept exercise is meant to teach an understanding/use of `unpacking` and - `numbers` - `strings` - `tuples` - +- `loops` ## Representer @@ -66,4 +59,4 @@ This exercise does not require any specific logic to be added to the [represente This exercise does not require any specific logic to be added to the [analyzer][analyzer]. [analyzer]: https://github.com/exercism/python-analyzer -[representer]: https://github.com/exercism/python-representer \ No newline at end of file +[representer]: https://github.com/exercism/python-representer diff --git a/exercises/concept/locomotive-engineer/.meta/exemplar.py b/exercises/concept/locomotive-engineer/.meta/exemplar.py index 6fdc680ab0..bda295d269 100644 --- a/exercises/concept/locomotive-engineer/.meta/exemplar.py +++ b/exercises/concept/locomotive-engineer/.meta/exemplar.py @@ -2,6 +2,12 @@ def get_list_of_wagons(*args): + """Return a list of wagons. + + :param *args: arbitrary number of wagons. + :return: list - list of wagons. + """ + return list(args) @@ -12,7 +18,9 @@ def fix_list_of_wagons(each_wagons_id, missing_wagons): :param missing_wagons: list - the list of missing wagons. :return: list - list of wagons. """ + first, second, locomotive, *rest = each_wagons_id + return [locomotive, *missing_wagons, *rest, first, second] @@ -20,19 +28,21 @@ def add_missing_stops(route, **kwargs): """Add missing stops to route dict. :param route: dict - the dict of routing information. - :param **kwargs: arbitrary number of stops. + :param **kwargs: arbitrary number of stops. :return: dict - updated route dictionary. """ + return {**route, "stops": list(kwargs.values())} def extend_route_information(route, more_route_information): - """Extend the route information with the more_route_information. + """Extend route information with more_route_information. :param route: dict - the route information. - :param more_route_information: dict - extra route information. + :param more_route_information: dict - extra route information. :return: dict - extended route information. """ + return {**route, **more_route_information} diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer.py b/exercises/concept/locomotive-engineer/locomotive_engineer.py index 6809958b3f..4166a24b68 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer.py @@ -1,6 +1,14 @@ """Functions which helps the locomotive engineer to keep track of the train.""" + # TODO: define the 'get_list_of_wagons' function +def get_list_of_wagons(): + """Return a list of wagons. + + :param: arbitrary number of wagons. + :return: list - list of wagons. + """ + pass # TODO: define the 'fixListOfWagons()' function @@ -15,11 +23,19 @@ def fix_list_of_wagons(each_wagons_id, missing_wagons): # TODO: define the 'add_missing_stops()' function +def add_missing_stops(): + """Add missing stops to route dict. + + :param route: dict - the dict of routing information. + :param: arbitrary number of stops. + :return: dict - updated route dictionary. + """ + pass # TODO: define the 'extend_route_information()' function def extend_route_information(route, more_route_information): - """Extend the route information with the more_route_information. + """Extend route information with more_route_information. :param route: dict - the route information. :param more_route_information: dict - extra route information. diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py index d90198ac47..7cc8425102 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py @@ -6,7 +6,7 @@ extend_route_information, fix_wagon_depot) -class InventoryTest(unittest.TestCase): +class LocomotiveEngineerTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_get_list_of_wagons(self): @@ -19,12 +19,14 @@ def test_get_list_of_wagons(self): self.assertEqual(get_list_of_wagons(*input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=2) - def test_fix_list_of_wagons(self): # One extra case needed at first - input_data = [([3, 27, 1, 14, 10, 4, 12, 6, 23, 17, 13, 22, 28, 19], [8, 10, 5, 9, 36, 7, 20]), + def test_fix_list_of_wagons(self): + input_data = [([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15]), + ([3, 27, 1, 14, 10, 4, 12, 6, 23, 17, 13, 22, 28, 19], [8, 10, 5, 9, 36, 7, 20]), ([4, 2, 1], [8, 6, 15]), ([3, 14, 1, 25, 7, 19, 10], [8, 6, 4, 5, 9, 21, 2, 13]) ] - output_data = [[1, 8, 10, 5, 9, 36, 7, 20, 14, 10, 4, 12, 6, 23, 17, 13, 22, 28, 19, 3, 27], + output_data = [[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5], + [1, 8, 10, 5, 9, 36, 7, 20, 14, 10, 4, 12, 6, 23, 17, 13, 22, 28, 19, 3, 27], [1, 8, 6, 15, 4, 2], [1, 8, 6, 4, 5, 9, 21, 2, 13, 25, 7, 19, 10, 3, 14] ] From dca06a41db31cfefbf24089c2a02ebb75376cdd5 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 27 Nov 2022 23:32:38 +0100 Subject: [PATCH 050/826] Major changes --- .../about.md | 116 ++++++++++-------- .../introduction.md | 1 - .../links.json | 2 +- config.json | 2 +- .../locomotive-engineer/.docs/hints.md | 13 +- .../locomotive_engineer_test.py | 3 +- 6 files changed, 72 insertions(+), 65 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 6212e4d944..b260ba4259 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -2,15 +2,17 @@ Unpacking refers to the act of extracting the elements of a collection, such as a `list`, `tuple`, or `dict`, using iteration. Unpacked values can be assigned to variables within the same step. -With unpacking, there are some special operators used: `*` and `**`. -When unpacking a list or tuple, the `*` operator can be used to assign all the remaining elements to a variable. -When unpacking a dictionary, the `**` operator can be used to assign all the remaining key-value pairs to a variable. +With unpacking the `*` operator is used. -When these operators are used without a collection they will _pack_ a number of values into a `list`, `tuple`, or dictionary. -It is common to use this kind of behavior when creating functions that take an arbitrary number of arguments. +When unpacking a list or tuple, `*` can be used to assign all the leftover elements to a variable. +When the `*` operator is used without a collection, it _packs_ a number of values into a `list` or `tuple`. +`**` can be used to combine multiple dictionaries into one dictionary. -Multiple assignment is the ability to assign multiple variables in one line. -This is done by separating the variables with a comma. +It is common in Python to also exploit this unpacking/packing behavior when defining functions that take an arbitrary number of positional or keyword arguments. +You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` + +[Multiple assignment][multiple assignment] is the ability to assign multiple variables in one statement. +This allows for code to be more concise and readable, and is done by separating the variables with a comma. ```exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. @@ -18,9 +20,7 @@ This is done by separating the variables with a comma. ## Multiple assignment -[Multiple assignment][multiple assignment] is the ability to assign multiple variables in one line. -This allows for code to be more concise and readable. -There has to be x number of variables on the left side of the `=` sign and x number of values on the right side of the `=` sign. +In multiple assignment since you are assigning a number of variables in one statement, the number of variables on the left side of the assignment operator must match the number of values on the right side. To separate the values, use a comma `,`: ```python @@ -29,8 +29,15 @@ To separate the values, use a comma `,`: 1 ``` -Multiple assignment is not limited to one data type but can instead be used with any data type. -For example: +If multiple assignment gets incorrect number of variables for the values given, you will get a `ValueError`: + +```python +>>> x, y, z = 1, 2 + +ValueError: too many values to unpack (expected 3, got 2) +``` + +Multiple assignment is not limited to one data type: ```python >>> x, y, z = 1, "Hello", True @@ -44,7 +51,7 @@ For example: True ``` -Multiple assignment also allows for the swapping of elements in `lists`. +Multiple assignment can be used to swap elements in `lists`. This practice is pretty common in [sorting algorithms][sorting algorithms]. For example: @@ -55,26 +62,16 @@ For example: [2, 1] ``` -It is also possible to assign multiple variables to the same value: - -```python ->>> a = b = 1 ->>> a -1 ->>> b -1 -``` +Since `tuples` are immutable, you can't swap elements in a `tuple`. ## Unpacking ```exercism/note -The examples below use lists but the same concepts apply to tuples. +The examples below use `lists` but the same concepts apply to `tuples`. ``` -In Python, it is possible to [unpack a `list`/`tuple`/`dictionary`][unpacking] into distinct variables. -Since values appear within lists in a specific order, it is therefore possible to _unpack_ a `list` into variables in the same order. - -Unpacking a list into variables: +In Python, it is possible to [unpack the elements of `list`/`tuple`/`dictionary`][unpacking] into distinct variables. +Since values appear within `lists`/`tuples` in a specific order, they are unpacked into variables in the same order: ```python >>> fruits = ["apple", "banana", "cherry"] @@ -83,7 +80,7 @@ Unpacking a list into variables: "apple" ``` -If there are values that are not needed then you can use `_` to ignore those values: +If there are values that are not needed then you can use `_` to flag them: ```python >>> fruits = ["apple", "banana", "cherry"] @@ -92,7 +89,9 @@ If there are values that are not needed then you can use `_` to ignore those val "cherry" ``` -You can also do [deep unpacking][deep unpacking] on a `list`, which assigns values from a `list` within a `list` (_this is also known as nested list unpacking_): +### Deep unpacking + +Unpacking and assigning values from a `list`/`tuple` inside of a `list` or `tuple` (_also known as nested lists/tuples_), works in the same way a shallow unpacking does, but often needs qualifiers to clarify the values context or position: ```python >>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] @@ -104,7 +103,7 @@ You can also do [deep unpacking][deep unpacking] on a `list`, which assigns valu "potato" ``` -Deep unpacking and normal unpacking can be mixed together: +You can also deeply unpack just a portion of a nested `list`/`tuple`: ```python >>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] @@ -127,9 +126,9 @@ ValueError: too many values to unpack (expected 1) ### Unpacking a list/tuple with `*` -When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the remainder values. -This can be used instead of slicing the `list`/`tuple`, which in some situations could be more readable. -For example, we can extract the first element below and then pack the remaining values into a new `list` without the first element: +When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the "leftover" values. +This is clearer than slicing the `list`/`tuple` (_which in some situations is less readable_). +For example, we can extract the first element and then assign the remaining values into a new `list` without the first element: ```python >>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] @@ -168,8 +167,8 @@ We can also use `*` in deep unpacking: ### Unpacking a dictionary [Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`. -Iteration over dictionaries defaults to the `keys`. -So when unpacking a `dict`, you can only unpack the `keys` and not the `values`: +Iteration over dictionaries defaults to the **keys**. +So when unpacking a `dict`, you can only unpack the **keys** and not the **values**: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -187,8 +186,8 @@ If you want to unpack the values then you can use the `values()` method: 6 ``` -If both `keys` and `values` are needed, use the `items()` method. -Using `items()` will generate tuples with `key-value` pairs. +If both **keys** and **values** are needed, use the `items()` method. +Using `items()` will generate tuples with **key-value** pairs. This is because [`dict.items()` generates an iterable with key-value `tuples`][items]. ```python @@ -200,23 +199,23 @@ This is because [`dict.items()` generates an iterable with key-value `tuples`][i ## Packing -As with unpacking, _packing_ uses the same `*` and `**` operators. -[Packing][packing and unpacking]] is the ability to group multiple values into one variable. -This is useful for when you want to _unpack_ values, make changes, and then _pack_ the results back into a variable. +[Packing][packing and unpacking] is the ability to group multiple values into one variable. +This is useful when you want to _unpack_ values, make changes, and then _pack_ the results back into a variable. It also makes it possible to perform merges on 2 or more `lists`/`tuples`/`dicts`. ### Packing a list/tuple with `*` -Packing a `list`/`tuple` is done by using the `*` operator -This will pack all the variables into a list/tuple. +Packing a `list`/`tuple` can be done using the `*` operator. +This will pack all the values into a `list`/`tuple`. ```python >>> fruits = ["apple", "banana", "cherry"] >>> more_fruits = ["orange", "kiwi", "melon", "mango"] ->>> combined_fruits_lists = [*fruits, *more_fruits] - +# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits_lists +>>> combined_fruits_lists = *fruits, *more_fruits +# If the unpacking is on the right side of "=" then it results in a tuple >>> combined_fruits_lists -["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] +("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango") ``` ### Packing a dictionary with `**` @@ -227,8 +226,9 @@ This will pack all the variables into a dictionary. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} >>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3} +# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs >>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory} - +# then the pairs are packed into combined_fruits_inventory >>> combined_fruits_inventory {"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3} ``` @@ -237,21 +237,25 @@ This will pack all the variables into a dictionary. ### Packing with function parameters -When you have a function that accepts an arbitrary or large number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] to pack or group those parameters together. -`*args` is used for packing/signaling an arbitrary number of positional (non-keyworded) arguments. -`**kwargs` is used for packing/signaling an arbitrary number of keyword arguments to a function. +When you have a function that accepts an arbitrary number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] in the function definition. +`*args` is used to pack an arbitrary number of positional (non-keyworded) arguments and +`**kwargs` is used to pack an arbitrary number of keyword arguments. Usage of `*args`: ```python +# This function is defined to take any number of positional arguments + >>> def my_function(*args): ... print(args) +# Arguments given to the function are packed into a tuple + >>> my_function(1, 2, 3) (1, 2, 3) ->>> my_function("Hello", "World") -("Hello", "World") +>>> my_function("Hello") +("Hello") >>> my_function(1, 2, 3, "Hello", "Mars") (1, 2, 3, "Hello", "Mars") @@ -260,9 +264,13 @@ Usage of `*args`: Usage of `**kwargs`: ```python +# This function is defined to take any number of keyword arguments + >>> def my_function(**kwargs): ... print(kwargs) +# Arguments given to the function are packed into a dictionary + >>> my_function(a=1, b=2, c=3) {"a": 1, "b": 2, "c": 3} ``` @@ -273,7 +281,7 @@ Usage of `**kwargs`: >>> def my_function(*args, **kwargs): ... print(sum(args)) ... for key, value in kwargs.items(): -... print(f"{key} = {value}") +... print(str(key) + " = " + str(value)) >>> my_function(1, 2, 3, a=1, b=2, c=3) 6 @@ -321,7 +329,7 @@ TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b' ### Unpacking into function calls You can use `*` to unpack a `list`/`tuple` of arguments into a function call. -This is very useful for functions that don't accept an `iterable` or `iterator`: +This is very useful for functions that don't accept an `iterable`: ```python >>> def my_function(a, b, c): @@ -337,7 +345,7 @@ numbers = [1, 2, 3] ``` Using `*` unpacking with the `zip()` function is another common use case. -Since `zip()` takes multiple iterables and returns a list of tuples with the values from each iterable grouped: +Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped: ```python >>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True]) diff --git a/concepts/unpacking-and-multiple-assignment/introduction.md b/concepts/unpacking-and-multiple-assignment/introduction.md index 0bb09dee5c..a2b190656e 100644 --- a/concepts/unpacking-and-multiple-assignment/introduction.md +++ b/concepts/unpacking-and-multiple-assignment/introduction.md @@ -4,7 +4,6 @@ Unpacking refers to the act of extracting the elements of a collection, such as Unpacked values can be assigned to variables within the same step. With unpacking, there are some special operators used: `*` and `**`. - When unpacking a list or tuple, the `*` operator can be used to assign all the remaining elements to a variable. When unpacking a dictionary, the `**` operator can be used to assign all the remaining key-value pairs to a variable. When these operators are used without a collection, they _pack_ a number of values into a `list`, `tuple`, or `dict`. diff --git a/concepts/unpacking-and-multiple-assignment/links.json b/concepts/unpacking-and-multiple-assignment/links.json index 8caff2f9cd..6ad50c216c 100644 --- a/concepts/unpacking-and-multiple-assignment/links.json +++ b/concepts/unpacking-and-multiple-assignment/links.json @@ -1,7 +1,7 @@ [ { "url": "https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/", - "description": "Trey Hunner: Astrisks in Python - What they are and How to Use Them." + "description": "Trey Hunner: Asterisks in Python - What they are and How to Use Them." }, { "url": "https://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/", diff --git a/config.json b/config.json index 61cc1285f0..7350c2d7c8 100644 --- a/config.json +++ b/config.json @@ -142,7 +142,7 @@ "uuid": "e1b8b9c9-21c3-47b1-b645-5938b3110c78", "concepts": ["unpacking-and-multiple-assignment"], "prerequisites": ["loops", "lists", "tuples", "dicts"], - "status": "wip" + "status": "beta" }, { "slug": "cater-waiter", diff --git a/exercises/concept/locomotive-engineer/.docs/hints.md b/exercises/concept/locomotive-engineer/.docs/hints.md index 929448f286..6bfae5f3e3 100644 --- a/exercises/concept/locomotive-engineer/.docs/hints.md +++ b/exercises/concept/locomotive-engineer/.docs/hints.md @@ -2,7 +2,7 @@ ## General -- To extract multiple arguments in the function parameters so can you pack them with the `*args` operator for list or tuples or `kwargs` for keyword-based arguments. +- To extract multiple arguments in the function parameters so can you pack them with the `*args` operator for `list` or `tuples` or `**kwargs` for keyword-based arguments. - To pack or unpack use the `*` or `**` operator. ## 1. Create a list of all wagons @@ -11,25 +11,24 @@ ## 2. Fix list of wagons -- Using unpacking with the `*` operator, lets you extract the first two elements of a list while keeping the rest intact. -- To add another list into an existing list, you can use the `*` operator to "spread" the list. +- Using unpacking with the `*` operator, lets you extract the first two elements of a `list` while keeping the rest intact. +- To add another `list` into an existing `list`, you can use the `*` operator to "spread" the `list`. ## 3. Add missing stops - Using `**kwargs` as a function parameter will allow an arbitrary amount of keyword arguments to be passed. -- Using `**(dict)` as an argument will unpack a dictionary into keyword arguments. +- Using `**` as an argument will unpack a dictionary into keyword arguments. - You can put keyword arguments in a `{}` or `dict()`. - To get the values out of a dictionary, you can use the `.values()` method. ## 4. Extend routing information -- Using `**(dict)` as an argument will unpack a dictionary into keyword arguments. +- Using `**` as an argument will unpack a dictionary into keyword arguments. - You can put keyword arguments in a `{}` or `dict()`. ## 5. Fix the wagon depot -- `zip(*iterators)` can use used to transpose a nested list. +- `zip(*iterators)` can use used to transpose a nested `list`. - To extract data from zipped iterators, you can use a for loop. - you can also unpack zipped iterators using `*`. `[*content] = zip(iterator_1, iterator_2)` will unzip the `tuple` produced by `zip()` into a `list`. - diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py index 7cc8425102..b3a46939fd 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py @@ -6,6 +6,7 @@ extend_route_information, fix_wagon_depot) + class LocomotiveEngineerTest(unittest.TestCase): @pytest.mark.task(taskno=1) @@ -86,4 +87,4 @@ def test_fix_wagon_depot(self): for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different wagon depot list instead.' - self.assertEqual(fix_wagon_depot(input_data), output_data, msg=error_msg) \ No newline at end of file + self.assertEqual(fix_wagon_depot(input_data), output_data, msg=error_msg) From 06f7c95fcfc6a93e501e40302447d4100fd4842a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 27 Nov 2022 21:39:40 -0800 Subject: [PATCH 051/826] Final Rewrites for Concept and Exercise docs Concept introduction.md about.md Exercise introduction.md --- .../about.md | 58 +++--- .../introduction.md | 23 ++- .../locomotive-engineer/.docs/introduction.md | 178 +++++++++++++----- 3 files changed, 175 insertions(+), 84 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index b260ba4259..f10d377075 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -1,18 +1,22 @@ # Unpacking and Multiple Assignment Unpacking refers to the act of extracting the elements of a collection, such as a `list`, `tuple`, or `dict`, using iteration. -Unpacked values can be assigned to variables within the same step. -With unpacking the `*` operator is used. +Unpacked values can then be assigned to variables within the same statement. +A very common example of this behavior is `for item in list`, where `item` takes on the value of each `list` element in turn throughout the iteration. -When unpacking a list or tuple, `*` can be used to assign all the leftover elements to a variable. -When the `*` operator is used without a collection, it _packs_ a number of values into a `list` or `tuple`. -`**` can be used to combine multiple dictionaries into one dictionary. +[Multiple assignment][multiple assignment] is the ability to assign multiple variables to unpacked values within one statement. +This allows for code to be more concise and readable, and is done by separating the variables to be assigned with a comma such as `first, second, third = (1,2,3)` or `for index, item in enumerate(iterable)`. -It is common in Python to also exploit this unpacking/packing behavior when defining functions that take an arbitrary number of positional or keyword arguments. -You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` +The special operators `*` and `**` are often used in unpacking contexts. +`*` can be used to combine multiple `lists`/`tuples` into one `list`/`tuple` by _unpacking_ each into a new common `list`/`tuple`. +`**` can be used to combine multiple dictionaries into one dictionary by _unpacking_ each into a new common `dict`. + +When the `*` operator is used without a collection,it _packs_ a number of values into a `list`. +This is often used in multiple assignment to group all "leftover" elements that do not have individual assignments into a single variable. + +It is common in Python to also exploit this unpacking/packing behavior when using or defining functions that take an arbitrary number of positional or keyword arguments. +You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` and the "special" arguments used as `some_function(*some_tuple, **some_dict)`. -[Multiple assignment][multiple assignment] is the ability to assign multiple variables in one statement. -This allows for code to be more concise and readable, and is done by separating the variables with a comma. ```exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. @@ -20,7 +24,7 @@ This allows for code to be more concise and readable, and is done by separating ## Multiple assignment -In multiple assignment since you are assigning a number of variables in one statement, the number of variables on the left side of the assignment operator must match the number of values on the right side. +In multiple assignment, the number of variables on the left side of the assignment operator (`=`) must match the number of values on the right side. To separate the values, use a comma `,`: ```python @@ -29,7 +33,7 @@ To separate the values, use a comma `,`: 1 ``` -If multiple assignment gets incorrect number of variables for the values given, you will get a `ValueError`: +If the multiple assignment gets an incorrect number of variables for the values given, a `ValueError` will be thrown: ```python >>> x, y, z = 1, 2 @@ -64,6 +68,7 @@ For example: Since `tuples` are immutable, you can't swap elements in a `tuple`. + ## Unpacking ```exercism/note @@ -199,7 +204,7 @@ This is because [`dict.items()` generates an iterable with key-value `tuples`][i ## Packing -[Packing][packing and unpacking] is the ability to group multiple values into one variable. +[Packing][packing and unpacking] is the ability to group multiple values into one `list` that is assigned to a variable. This is useful when you want to _unpack_ values, make changes, and then _pack_ the results back into a variable. It also makes it possible to perform merges on 2 or more `lists`/`tuples`/`dicts`. @@ -209,35 +214,44 @@ Packing a `list`/`tuple` can be done using the `*` operator. This will pack all the values into a `list`/`tuple`. ```python ->>> fruits = ["apple", "banana", "cherry"] +>>> fruits = ("apple", "banana", "cherry") >>> more_fruits = ["orange", "kiwi", "melon", "mango"] -# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits_lists ->>> combined_fruits_lists = *fruits, *more_fruits -# If the unpacking is on the right side of "=" then it results in a tuple ->>> combined_fruits_lists + +# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits +>>> combined_fruits = *fruits, *more_fruits + +# If there is no * on to the left of the "=" the result is a tuple +>>> combined_fruits ("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango") + +# If the * operator is used on the left side of "=" the result is a list +>>> *combined_fruits_too, = *fruits, *more_fruits +>>> combined_fruits_too +['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango'] ``` ### Packing a dictionary with `**` Packing a dictionary is done by using the `**` operator. -This will pack all the variables into a dictionary. +This will pack all **key**-**value** pairs from one dictionary into another dictionary, or combine two dictionarys together. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} >>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3} -# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs + +# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined. >>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory} + # then the pairs are packed into combined_fruits_inventory >>> combined_fruits_inventory {"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3} ``` -## Usage of `*` and `**` with a function +## Usage of `*` and `**` with functions ### Packing with function parameters -When you have a function that accepts an arbitrary number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] in the function definition. +When you create a function that accepts an arbitrary number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] in the function definition. `*args` is used to pack an arbitrary number of positional (non-keyworded) arguments and `**kwargs` is used to pack an arbitrary number of keyword arguments. @@ -355,10 +369,8 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the ``` [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ -[deep unpacking]: https://mathspp.com/blog/pydonts/deep-unpacking#deep-unpacking [items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ -[positional and keyword arguments]: https://www.python-engineer.com/courses/advancedpython/18-function-arguments/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp diff --git a/concepts/unpacking-and-multiple-assignment/introduction.md b/concepts/unpacking-and-multiple-assignment/introduction.md index a2b190656e..d8b91bd9c0 100644 --- a/concepts/unpacking-and-multiple-assignment/introduction.md +++ b/concepts/unpacking-and-multiple-assignment/introduction.md @@ -1,21 +1,26 @@ # Unpacking and Multiple Assignment Unpacking refers to the act of extracting the elements of a collection, such as a `list`, `tuple`, or `dict`, using iteration. -Unpacked values can be assigned to variables within the same step. -With unpacking, there are some special operators used: `*` and `**`. +Unpacked values can then be assigned to variables within the same statement. +A very common example of this behavior is `for item in list`, where `item` takes on the value of each `list` element in turn throughout the iteration. -When unpacking a list or tuple, the `*` operator can be used to assign all the remaining elements to a variable. -When unpacking a dictionary, the `**` operator can be used to assign all the remaining key-value pairs to a variable. -When these operators are used without a collection, they _pack_ a number of values into a `list`, `tuple`, or `dict`. +[Multiple assignment][multiple assignment] is the ability to assign multiple variables to unpacked values within one statement. +This allows for code to be more concise and readable, and is done by separating the variables to be assigned with a comma such as `first, second, third = (1,2,3)` or `for index, item in enumerate(iterable)`. -It is common in Python to also exploit this unpacking/packing behavior when defining functions that take an arbitrary number of positional or keyword arguments. -You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` +The special operators `*` and `**` are often used in unpacking contexts. +`*` can be used to combine multiple `lists`/`tuples` into one `list`/`tuple` by _unpacking_ each into a new common `list`/`tuple`. +`**` can be used to combine multiple dictionaries into one dictionary by _unpacking_ each into a new common `dict`. + +When the `*` operator is used without a collection,it _packs_ a number of values into a `list`. +This is often used in multiple assignment to group all "leftover" elements that do not have individual assignments into a single variable. + +It is common in Python to also exploit this unpacking/packing behavior when using or defining functions that take an arbitrary number of positional or keyword arguments. +You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` and the "special" arguments used as `some_function(*some_tuple, **some_dict)`. -[Multiple assignment][multiple assignment] is the ability to assign multiple variables in one line. -This allows for code to be more concise and readable, and is done by separating the variables with a comma. ```exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. ``` + [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 779a6e0504..dcda62efe8 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -1,16 +1,9 @@ # Unpacking and Multiple Assignment Unpacking refers to the act of extracting the elements of a collection, such as a `list`, `tuple`, or `dict`, using iteration. -Unpacked values can be assigned to variables within the same step. -With unpacking, there are some special operators used: `*` and `**`. -When unpacking a list or tuple, the `*` operator can be used to assign all the remaining elements to a variable. -When unpacking a dictionary, the `**` operator can be used to assign all the remaining key-value pairs to a variable. +Unpacked values can then be assigned to variables within the same statement, which is commonly referred to as [Multiple assignment][multiple assignment]. -When these operators are used without a collection they will _pack_ a number of values into a `list`, `tuple`, or dictionary. -It is common to use this kind of behavior when creating functions that take an arbitrary number of arguments. - -Multiple assignment is the ability to assign multiple variables in one line. -This is done by separating the variables with a comma. +The special operators `*` and `**` are often used in unpacking contexts and with multiple assignment. ```exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. @@ -18,9 +11,7 @@ This is done by separating the variables with a comma. ## Multiple assignment -[Multiple assignment][multiple assignment] is the ability to assign multiple variables in one line. -This allows for code to be more concise and readable. -There has to be x number of variables on the left side of the `=` sign and x number of values on the right side of the `=` sign. +In multiple assignment, the number of variables on the left side of the assignment operator (`=`) must match the number of values on the right side. To separate the values, use a comma `,`: ```python @@ -29,31 +20,50 @@ To separate the values, use a comma `,`: 1 ``` -Multiple assignment is not limited to one data type but can instead be used with any data type. -For example: +If the multiple assignment gets an incorrect number of variables for the values given, a `ValueError` will be thrown: ```python ->>> a, b, c = 1, "Hello", True ->>> a +>>> x, y, z = 1, 2 + +ValueError: too many values to unpack (expected 3, got 2) +``` + +Multiple assignment is not limited to one data type: + +```python +>>> x, y, z = 1, "Hello", True +>>> x 1 ->>> b +>>> y 'Hello' ->>> c +>>> z True ``` +Multiple assignment can be used to swap elements in `lists`. +This practice is pretty common in [sorting algorithms][sorting algorithms]. +For example: + +```python +>>> numbers = [1, 2] +>>> numbers[0], numbers[1] = numbers[1], numbers[0] +>>> numbers +[2, 1] +``` + +Since `tuples` are immutable, you can't swap elements in a `tuple`. + + ## Unpacking ```exercism/note -The examples below use lists but the same concepts apply to tuples. +The examples below use `lists` but the same concepts apply to `tuples`. ``` -In Python, it is possible to [unpack a `list`/`tuple`/`dictionary`][unpacking] into distinct variables. -Since values appear within lists in a specific order, it is therefore possible to _unpack_ a `list` into variables in the same order. - -Unpacking a list into variables: +In Python, it is possible to [unpack the elements of `list`/`tuple`/`dictionary`][unpacking] into distinct variables. +Since values appear within `lists`/`tuples` in a specific order, they are unpacked into variables in the same order: ```python >>> fruits = ["apple", "banana", "cherry"] @@ -62,7 +72,7 @@ Unpacking a list into variables: "apple" ``` -If there are values that are not needed then you can use `_` to ignore those values: +If there are values that are not needed then you can use `_` to flag them: ```python >>> fruits = ["apple", "banana", "cherry"] @@ -71,7 +81,9 @@ If there are values that are not needed then you can use `_` to ignore those val "cherry" ``` -You can also do [deep unpacking][deep unpacking] on a `list`, which assigns values from a `list` within a `list` (_this is also known as nested list unpacking_): +### Deep unpacking + +Unpacking and assigning values from a `list`/`tuple` inside of a `list` or `tuple` (_also known as nested lists/tuples_), works in the same way a shallow unpacking does, but often needs qualifiers to clarify the values context or position: ```python >>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] @@ -83,7 +95,7 @@ You can also do [deep unpacking][deep unpacking] on a `list`, which assigns valu "potato" ``` -Deep unpacking and normal unpacking can be mixed together: +You can also deeply unpack just a portion of a nested `list`/`tuple`: ```python >>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] @@ -106,9 +118,9 @@ ValueError: too many values to unpack (expected 1) ### Unpacking a list/tuple with `*` -When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the remainder values. -This can be used instead of slicing the `list`/`tuple`, which in some situations could be more readable. -For example, we can extract the first element below and then pack the remaining values into a new `list` without the first element: +When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the "leftover" values. +This is clearer than slicing the `list`/`tuple` (_which in some situations is less readable_). +For example, we can extract the first element and then assign the remaining values into a new `list` without the first element: ```python >>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] @@ -132,11 +144,24 @@ We can also extract the values at the beginning and end of the `list` while grou ["banana", "cherry", "orange", "kiwi"] ``` +We can also use `*` in deep unpacking: + +```python +>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]] +>>> [[a, *rest], b] = fruits_vegetables +>>> a +"apple" + +>>> rest +["banana", "melon"] +``` + ### Unpacking a dictionary [Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`. -Iteration over dictionaries defaults to the `keys`. -So when unpacking a `dict`, you can only unpack the `keys` and not the `values`: +Iteration over dictionaries defaults to the **keys**. +So when unpacking a `dict`, you can only unpack the **keys** and not the **values**: + ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} >>> x, y, z = fruits_inventory @@ -153,60 +178,85 @@ If you want to unpack the values then you can use the `values()` method: 6 ``` +If both **keys** and **values** are needed, use the `items()` method. +Using `items()` will generate tuples with **key-value** pairs. +This is because [`dict.items()` generates an iterable with key-value `tuples`][items]. + +```python +>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} +>>> x, y, z = fruits_inventory.items() +>>> x +("apple", 6) +``` + ## Packing -As with unpacking, _packing_ uses the same `*` and `**` operators. -[Packing][packing and unpacking]] is the ability to group multiple values into one variable. -This is useful for when you want to _unpack_ values, make changes, and then _pack_ the results back into a variable. +[Packing][packing and unpacking] is the ability to group multiple values into one `list` that is assigned to a variable. +This is useful when you want to _unpack_ values, make changes, and then _pack_ the results back into a variable. It also makes it possible to perform merges on 2 or more `lists`/`tuples`/`dicts`. ### Packing a list/tuple with `*` -Packing a `list`/`tuple` is done by using the `*` operator -This will pack all the variables into a list/tuple. +Packing a `list`/`tuple` can be done using the `*` operator. +This will pack all the values into a `list`/`tuple`. ```python ->>> fruits = ["apple", "banana", "cherry"] +>>> fruits = ("apple", "banana", "cherry") >>> more_fruits = ["orange", "kiwi", "melon", "mango"] ->>> combined_fruits_lists = [*fruits, *more_fruits] ->>> combined_fruits_lists -["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] +# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits +>>> combined_fruits = *fruits, *more_fruits + +# If there is no * on to the left of the "=" the result is a tuple +>>> combined_fruits +("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango") + +# If the * operator is used on the left side of "=" the result is a list +>>> *combined_fruits_too, = *fruits, *more_fruits +>>> combined_fruits_too +['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango'] ``` ### Packing a dictionary with `**` Packing a dictionary is done by using the `**` operator. -This will pack all the variables into a dictionary. +This will pack all **key**-**value** pairs from one dictionary into another dictionary, or combine two dictionarys together. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} >>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3} + +# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined. >>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory} +# then the pairs are packed into combined_fruits_inventory >>> combined_fruits_inventory {"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3} ``` -## Usage of `*` and `**` with a function +## Usage of `*` and `**` with functions ### Packing with function parameters -When you have a function that accepts an arbitrary or large number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] to pack or group those parameters together. -`*args` is used for packing/signaling an arbitrary number of positional (non-keyworded) arguments. -`**kwargs` is used for packing/signaling an arbitrary number of keyword arguments to a function. +When you create a function that accepts an arbitrary number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] in the function definition. +`*args` is used to pack an arbitrary number of positional (non-keyworded) arguments and +`**kwargs` is used to pack an arbitrary number of keyword arguments. Usage of `*args`: ```python +# This function is defined to take any number of positional arguments + >>> def my_function(*args): ... print(args) +# Arguments given to the function are packed into a tuple + >>> my_function(1, 2, 3) (1, 2, 3) ->>> my_function("Hello", "World") -("Hello", "World") +>>> my_function("Hello") +("Hello") >>> my_function(1, 2, 3, "Hello", "Mars") (1, 2, 3, "Hello", "Mars") @@ -215,13 +265,35 @@ Usage of `*args`: Usage of `**kwargs`: ```python +# This function is defined to take any number of keyword arguments + >>> def my_function(**kwargs): ... print(kwargs) +# Arguments given to the function are packed into a dictionary + >>> my_function(a=1, b=2, c=3) {"a": 1, "b": 2, "c": 3} ``` +`*args` and `**kwargs` can also be used in combination with one another: + +```python +>>> def my_function(*args, **kwargs): +... print(sum(args)) +... for key, value in kwargs.items(): +... print(str(key) + " = " + str(value)) + +>>> my_function(1, 2, 3, a=1, b=2, c=3) +6 +a = 1 +b = 2 +c = 3 +``` + +You can also write parameters before `*args` to allow for specific positional arguments. +Individual keyword arguments then have to appear before `**kwargs`. + ```exercism/caution [Arguments have to be structured][Positional and keyword arguments] like this: @@ -258,7 +330,8 @@ TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b' ### Unpacking into function calls You can use `*` to unpack a `list`/`tuple` of arguments into a function call. -This is very useful for functions that don't accept an `iterable` or `iterator`: +This is very useful for functions that don't accept an `iterable`: + ```python >>> def my_function(a, b, c): ... print(c) @@ -273,7 +346,7 @@ numbers = [1, 2, 3] ``` Using `*` unpacking with the `zip()` function is another common use case. -Since `zip()` takes multiple iterables and returns a list of tuples with the values from each iterable grouped: +Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped: ```python >>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True]) @@ -282,8 +355,9 @@ Since `zip()` takes multiple iterables and returns a list of tuples with the val [('y', 2, False), ('z', 3, True)] ``` +[args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ +[items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ -[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp -[deep unpacking]: https://mathspp.com/blog/pydonts/deep-unpacking#deep-unpacking [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ -[args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ +[sorting algorithms]: https://realpython.com/sorting-algorithms-python/ +[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp From fd9d85f8033cf1c064cb79eddaaf55e76b3d5ead Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 28 Nov 2022 08:13:31 +0100 Subject: [PATCH 052/826] Spell fixes --- concepts/unpacking-and-multiple-assignment/about.md | 8 +++----- .../unpacking-and-multiple-assignment/introduction.md | 4 +--- .../concept/locomotive-engineer/.docs/introduction.md | 3 +-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index f10d377075..4e1e90a251 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -11,13 +11,12 @@ The special operators `*` and `**` are often used in unpacking contexts. `*` can be used to combine multiple `lists`/`tuples` into one `list`/`tuple` by _unpacking_ each into a new common `list`/`tuple`. `**` can be used to combine multiple dictionaries into one dictionary by _unpacking_ each into a new common `dict`. -When the `*` operator is used without a collection,it _packs_ a number of values into a `list`. +When the `*` operator is used without a collection, it _packs_ a number of values into a `list`. This is often used in multiple assignment to group all "leftover" elements that do not have individual assignments into a single variable. It is common in Python to also exploit this unpacking/packing behavior when using or defining functions that take an arbitrary number of positional or keyword arguments. You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` and the "special" arguments used as `some_function(*some_tuple, **some_dict)`. - ```exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. ``` @@ -68,7 +67,6 @@ For example: Since `tuples` are immutable, you can't swap elements in a `tuple`. - ## Unpacking ```exercism/note @@ -193,7 +191,7 @@ If you want to unpack the values then you can use the `values()` method: If both **keys** and **values** are needed, use the `items()` method. Using `items()` will generate tuples with **key-value** pairs. -This is because [`dict.items()` generates an iterable with key-value `tuples`][items]. +This is because of [`dict.items()` generates an iterable with key-value `tuples`][items]. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -233,7 +231,7 @@ This will pack all the values into a `list`/`tuple`. ### Packing a dictionary with `**` Packing a dictionary is done by using the `**` operator. -This will pack all **key**-**value** pairs from one dictionary into another dictionary, or combine two dictionarys together. +This will pack all **key**-**value** pairs from one dictionary into another dictionary, or combine two dictionaries together. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} diff --git a/concepts/unpacking-and-multiple-assignment/introduction.md b/concepts/unpacking-and-multiple-assignment/introduction.md index d8b91bd9c0..a4675a771e 100644 --- a/concepts/unpacking-and-multiple-assignment/introduction.md +++ b/concepts/unpacking-and-multiple-assignment/introduction.md @@ -11,16 +11,14 @@ The special operators `*` and `**` are often used in unpacking contexts. `*` can be used to combine multiple `lists`/`tuples` into one `list`/`tuple` by _unpacking_ each into a new common `list`/`tuple`. `**` can be used to combine multiple dictionaries into one dictionary by _unpacking_ each into a new common `dict`. -When the `*` operator is used without a collection,it _packs_ a number of values into a `list`. +When the `*` operator is used without a collection, it _packs_ a number of values into a `list`. This is often used in multiple assignment to group all "leftover" elements that do not have individual assignments into a single variable. It is common in Python to also exploit this unpacking/packing behavior when using or defining functions that take an arbitrary number of positional or keyword arguments. You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` and the "special" arguments used as `some_function(*some_tuple, **some_dict)`. - ```exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. ``` - [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index dcda62efe8..5e7c3327a3 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -55,7 +55,6 @@ For example: Since `tuples` are immutable, you can't swap elements in a `tuple`. - ## Unpacking ```exercism/note @@ -220,7 +219,7 @@ This will pack all the values into a `list`/`tuple`. ### Packing a dictionary with `**` Packing a dictionary is done by using the `**` operator. -This will pack all **key**-**value** pairs from one dictionary into another dictionary, or combine two dictionarys together. +This will pack all **key**-**value** pairs from one dictionary into another dictionary, or combine two dictionaries together. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} From 40542d20d0e72855d487044781af8f860ad7fafe Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 28 Nov 2022 08:17:31 +0100 Subject: [PATCH 053/826] Update introduction.md --- exercises/concept/locomotive-engineer/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 5e7c3327a3..103b5199a9 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -179,7 +179,7 @@ If you want to unpack the values then you can use the `values()` method: If both **keys** and **values** are needed, use the `items()` method. Using `items()` will generate tuples with **key-value** pairs. -This is because [`dict.items()` generates an iterable with key-value `tuples`][items]. +This is because of [`dict.items()` generates an iterable with key-value `tuples`][items]. ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} From 8350956bde2553da8c7850ec46e8399ab31878dc Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 28 Nov 2022 08:51:09 +0100 Subject: [PATCH 054/826] fixes (#3223) Co-authored-by: BethanyG --- concepts/unpacking-and-multiple-assignment/about.md | 1 + exercises/concept/locomotive-engineer/.docs/introduction.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 4e1e90a251..b61af5c845 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -366,6 +366,7 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [('y', 2, False), ('z', 3, True)] ``` +[positional and keyword arguments]: https://www.python-engineer.com/courses/advancedpython/18-function-arguments/ [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ [items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 103b5199a9..b89ad22929 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -354,6 +354,8 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [('y', 2, False), ('z', 3, True)] ``` + +[positional and keyword arguments]: https://www.python-engineer.com/courses/advancedpython/18-function-arguments/ [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ [items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ From 6de3e45a5242edad3ee69e598c5212b09dc10585 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:02:51 +0100 Subject: [PATCH 055/826] fixes --- concepts/unpacking-and-multiple-assignment/about.md | 2 +- exercises/concept/locomotive-engineer/.docs/introduction.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index b61af5c845..bce43b047d 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -306,7 +306,7 @@ You can also write parameters before `*args` to allow for specific positional ar Individual keyword arguments then have to appear before `**kwargs`. ```exercism/caution -[Arguments have to be structured][Positional and keyword arguments] like this: +[Arguments have to be structured][positional and keyword arguments] like this: `def my_function(, *args, , **kwargs)` diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index b89ad22929..aec5d75786 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -294,7 +294,7 @@ You can also write parameters before `*args` to allow for specific positional ar Individual keyword arguments then have to appear before `**kwargs`. ```exercism/caution -[Arguments have to be structured][Positional and keyword arguments] like this: +[Arguments have to be structured][oositional and keyword arguments] like this: `def my_function(, *args, , **kwargs)` @@ -354,7 +354,6 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [('y', 2, False), ('z', 3, True)] ``` - [positional and keyword arguments]: https://www.python-engineer.com/courses/advancedpython/18-function-arguments/ [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ [items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ From e236a38ead3d2720b37a9513e4818a40e5344ca5 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:04:18 +0100 Subject: [PATCH 056/826] Update introduction.md --- exercises/concept/locomotive-engineer/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index aec5d75786..655aad2518 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -294,7 +294,7 @@ You can also write parameters before `*args` to allow for specific positional ar Individual keyword arguments then have to appear before `**kwargs`. ```exercism/caution -[Arguments have to be structured][oositional and keyword arguments] like this: +[Arguments have to be structured][positional and keyword arguments] like this: `def my_function(, *args, , **kwargs)` From 83175bcd2802449de673cc84624f5b441f048fa8 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:19:27 +0100 Subject: [PATCH 057/826] fixes --- concepts/unpacking-and-multiple-assignment/about.md | 3 +-- exercises/concept/locomotive-engineer/.docs/introduction.md | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index bce43b047d..5623b2f034 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -306,7 +306,7 @@ You can also write parameters before `*args` to allow for specific positional ar Individual keyword arguments then have to appear before `**kwargs`. ```exercism/caution -[Arguments have to be structured][positional and keyword arguments] like this: +[Arguments have to be structured](https://www.python-engineer.com/courses/advancedpython/18-function-arguments/) like this: `def my_function(, *args, , **kwargs)` @@ -366,7 +366,6 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [('y', 2, False), ('z', 3, True)] ``` -[positional and keyword arguments]: https://www.python-engineer.com/courses/advancedpython/18-function-arguments/ [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ [items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 655aad2518..e010c07576 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -294,7 +294,7 @@ You can also write parameters before `*args` to allow for specific positional ar Individual keyword arguments then have to appear before `**kwargs`. ```exercism/caution -[Arguments have to be structured][positional and keyword arguments] like this: +[Arguments have to be structured](https://www.python-engineer.com/courses/advancedpython/18-function-arguments/) like this: `def my_function(, *args, , **kwargs)` @@ -354,7 +354,6 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [('y', 2, False), ('z', 3, True)] ``` -[positional and keyword arguments]: https://www.python-engineer.com/courses/advancedpython/18-function-arguments/ [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ [items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ From a43474fb13eae5921f07f054f4d7af0886cee426 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 04:18:23 -0600 Subject: [PATCH 058/826] Create config.json --- .../practice/grains/.approaches/config.json | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 exercises/practice/grains/.approaches/config.json diff --git a/exercises/practice/grains/.approaches/config.json b/exercises/practice/grains/.approaches/config.json new file mode 100644 index 0000000000..5b73df0e74 --- /dev/null +++ b/exercises/practice/grains/.approaches/config.json @@ -0,0 +1,28 @@ +{ + "introduction": { + "authors": ["bobahop"] + }, + "approaches": [ + { + "uuid": "b54a712d-e3f1-4d63-9425-7bbe2cb0bc43", + "slug": "exponentiation", + "title": "exponentiation", + "blurb": "Use exponentiation to raise 2 by a specified power.", + "authors": ["bobahop"] + }, + { + "uuid": "002a7924-faa4-42d8-b535-23da4dae034a", + "slug": "pow", + "title": "pow", + "blurb": "Use pow to raise 2 by a specified power.", + "authors": ["bobahop"] + }, + { + "uuid": "2f700645-aa46-46b3-8c43-2251420c3a9b", + "slug": "bit-shifting", + "title": "Bit-shifting", + "blurb": "Use bit-shifting to set the correct value.", + "authors": ["bobahop"] + } + ] +} From c352a0b752700132d2b5659488192da9f082d914 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 04:35:47 -0600 Subject: [PATCH 059/826] Create config.json --- exercises/practice/grains/.articles/config.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 exercises/practice/grains/.articles/config.json diff --git a/exercises/practice/grains/.articles/config.json b/exercises/practice/grains/.articles/config.json new file mode 100644 index 0000000000..95bf08b404 --- /dev/null +++ b/exercises/practice/grains/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "5fa8743e-3740-4e1e-b9d7-bda6856c3a08", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach to calculating Grains.", + "authors": ["bobahop"] + } + ] +} From b3fba8505986450ba67f410a5619d885bbfaf36b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 04:36:30 -0600 Subject: [PATCH 060/826] Create Benchmark.py --- .../.articles/performance/code/Benchmark.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 exercises/practice/grains/.articles/performance/code/Benchmark.py diff --git a/exercises/practice/grains/.articles/performance/code/Benchmark.py b/exercises/practice/grains/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..8fef0fad05 --- /dev/null +++ b/exercises/practice/grains/.articles/performance/code/Benchmark.py @@ -0,0 +1,94 @@ +import timeit + +loops = 1_000_000 + +val = timeit.timeit("""square(64)""", + """ +def square(number): + if number < 1 or number > 64: + raise ValueError("square must be between 1 and 64") + return 1 << (number - 1) + +def total(): + return (1 << 64) - 1 + +""", number=loops) / loops + +print(f"bit shifting square 64: {val}") + + +val = timeit.timeit("""total()""", + """ +def square(number): + if number < 1 or number > 64: + raise ValueError("square must be between 1 and 64") + return 1 << (number - 1) + +def total(): + return (1 << 64) - 1 + +""", number=loops) / loops + +print(f"bit shifting total 64: {val}") + +val = timeit.timeit("""square(64)""", + """ +def square(number): + if number < 1 or number > 64: + raise ValueError("square must be between 1 and 64") + + return 2**(number - 1) + +def total(): + return 2**64 - 1 + +""", number=loops) / loops + +print(f"exponentiation square 64: {val}") + + +val = timeit.timeit("""total()""", + """ +def square(number): + if number < 1 or number > 64: + raise ValueError("square must be between 1 and 64") + + return 2**(number - 1) + +def total(): + return 2**64 - 1 + +""", number=loops) / loops + +print(f"exponentiation total 64: {val}") + +val = timeit.timeit("""square(64)""", + """ +def square(number): + if number < 1 or number > 64: + raise ValueError("square must be between 1 and 64") + + return pow(2, number-1) + +def total(): + return pow(2, 64) - 1 + +""", number=loops) / loops + +print(f"pow square 64: {val}") + + +val = timeit.timeit("""total()""", + """ +def square(number): + if number < 1 or number > 64: + raise ValueError("square must be between 1 and 64") + + return pow(2, number-1) + +def total(): + return pow(2, 64) - 1 + +""", number=loops) / loops + +print(f"pow total 64: {val}") From 463fab709a28b1ec71169797874b5b32d08265f7 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 04:37:18 -0600 Subject: [PATCH 061/826] Create snippet.md --- .../practice/grains/.articles/performance/snippet.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 exercises/practice/grains/.articles/performance/snippet.md diff --git a/exercises/practice/grains/.articles/performance/snippet.md b/exercises/practice/grains/.articles/performance/snippet.md new file mode 100644 index 0000000000..b12b828c20 --- /dev/null +++ b/exercises/practice/grains/.articles/performance/snippet.md @@ -0,0 +1,8 @@ +``` +bit shifting square 64: 1.2760140001773835e-07 +bit shifting total 64: 6.096410000463947e-08 +exponentiation square 64: 4.122742000035942e-07 +exponentiation total 64: 6.094090000260621e-08 +pow square 64: 4.2468130000634117e-07 +pow total 64: 3.1965399999171494e-07 +``` From 7761d9ad195eb13a6add17995e6176de1c20ef62 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 04:55:37 -0600 Subject: [PATCH 062/826] Create content.md --- .../grains/.articles/performance/content.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 exercises/practice/grains/.articles/performance/content.md diff --git a/exercises/practice/grains/.articles/performance/content.md b/exercises/practice/grains/.articles/performance/content.md new file mode 100644 index 0000000000..740afca58d --- /dev/null +++ b/exercises/practice/grains/.articles/performance/content.md @@ -0,0 +1,44 @@ +# Performance + +In this approach, we'll find out how to most efficiently calculate the value for Grains in Python. + +The [approaches page][approaches] lists three idiomatic approaches to this exercise: + +1. [Using bit shifting][approach-bit-shifting] +2. [Using exponentiation][approach-exponentiation] +3. [Using `pow`][approach-pow] + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [timeit][timeit] library. + +``` +bit shifting square 64: 1.2760140001773835e-07 +bit shifting total 64: 6.096410000463947e-08 +exponentiation square 64: 4.122742000035942e-07 +exponentiation total 64: 6.094090000260621e-08 +pow square 64: 4.2468130000634117e-07 +pow total 64: 3.1965399999171494e-07 +``` + +- Bit shifting was the fastest for `square`. +- Bit shifting and exponentiation were about the same for `total`. +- Exponentiation and `pow` were about the same for `square`. +- `pow` was much significantly the slowest for `total`. + +Benchmarks were also done to substitute `if number not in range(1, 65):` for `if number < 1 or number > 64:`. + +``` +bit shifting square 64: 2.708769000018947e-07 +exponentiation square 64: 5.56936200009659e-07 +pow square 64: 5.738279999932274e-07 +``` + +Using `if number not in range(1, 65):` was over `125` nanoseconds longer than using `if number < 1 or number > 64:` for all approaches. + +[approaches]: https://exercism.org/tracks/python/exercises/grains/approaches +[approach-bit-shifting]: https://exercism.org/python/csharp/exercises/grains/approaches/bit-shifting +[approach-pow]: https://exercism.org/tracks/python/exercises/grains/approaches/pow +[approach-exponentiation]: https://exercism.org/tracks/python/exercises/grains/approaches/exponentiation +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/grains/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html From 590bf3654e30c2f529f1fc2ba4143390061f2847 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:11:51 -0600 Subject: [PATCH 063/826] Update content.md --- exercises/practice/grains/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/grains/.articles/performance/content.md b/exercises/practice/grains/.articles/performance/content.md index 740afca58d..b115001831 100644 --- a/exercises/practice/grains/.articles/performance/content.md +++ b/exercises/practice/grains/.articles/performance/content.md @@ -24,7 +24,7 @@ pow total 64: 3.1965399999171494e-07 - Bit shifting was the fastest for `square`. - Bit shifting and exponentiation were about the same for `total`. - Exponentiation and `pow` were about the same for `square`. -- `pow` was much significantly the slowest for `total`. +- `pow` was significantly the slowest for `total`. Benchmarks were also done to substitute `if number not in range(1, 65):` for `if number < 1 or number > 64:`. From 37cb8523282c55dc335cea13d15cdaa25814b62f Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:15:30 -0600 Subject: [PATCH 064/826] Create introduction.md --- .../grains/.approaches/introduction.md | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 exercises/practice/grains/.approaches/introduction.md diff --git a/exercises/practice/grains/.approaches/introduction.md b/exercises/practice/grains/.approaches/introduction.md new file mode 100644 index 0000000000..13f56d33be --- /dev/null +++ b/exercises/practice/grains/.approaches/introduction.md @@ -0,0 +1,91 @@ +# Introduction + +There are various idiomatic approaches to solve Grains. +You can use [bit shifting][bit-shifting] to calculate the number on grains on a square. +Or you can use [exponentiation][exponentiation]. +Another approach is to use [`pow`][pow]. + +## General guidance + +The key to solving Grains is to focus on each square having double the amount of grains as the square before it. +This means that the amount of grains grows exponentially. +The first square has one grain, which is `2` to the power of `0`. +The second square has two grains, which is `2` to the power of `1`. +The third square has four grains, which is `2` to the power of `2`. +You can see that the exponent, or power, that `2` is raised by is always one less than the square number. + +| Square | Power | Value | +| ------ | ----- | ----------------------- | +| 1 | 0 | 2 to the power of 0 = 1 | +| 2 | 1 | 2 to the power of 1 = 2 | +| 3 | 2 | 2 to the power of 2 = 4 | +| 4 | 3 | 2 to the power of 4 = 8 | + + +## Approach: Bit-shifting + +```python +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + + return 1 << number - 1 + + +def total(): + return (1 << 64) - 1 + +``` + +For more information, check the [bit-shifting approach][approach-bit-shifting]. + +## Approach: Exponentiation + +```python +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + + return 2 ** (number - 1) + + +def total(): + return 2 ** 64 - 1 + +``` + +For more information, check the [exponentiation approach][approach-exponentiation]. + +## Approach: `pow` + +```python +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + + return pow(2, number - 1) + + +def total(): + return pow(2, 64) - 1 + +``` + +For more information, check the [`pow` approach][approach-pow]. + +## Which approach to use? + +- Bit shifting is the fastest for `square`. +- Bit shifting and exponentiation are about the same for `total`. +- Exponentiation and `pow` are about the same for `square`. +- `pow` is significantly the slowest for `total`. + +For more information, check the [Performance article][article-performance]. + +[bit-shifting]: https://realpython.com/python-bitwise-operators/ +[exponentiation]: https://www.codingem.com/python-exponent-maths/ +[pow]: https://docs.python.org/3/library/functions.html#pow +[approach-bit-shifting]: https://exercism.org/tracks/python/exercises/grains/approaches/bit-shifting +[approach-exponentiation]: https://exercism.org/tracks/python/exercises/grains/approaches/exponentiation +[approach-pow]: https://exercism.org/tracks/python/exercises/grains/approaches/pow +[article-performance]: https://exercism.org/tracks/python/exercises/grains/articles/performance From 0033af4967d9ba3805995bf66de0ba85bcd81ffc Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:16:35 -0600 Subject: [PATCH 065/826] Create snippet.txt --- .../practice/grains/.approaches/bit-shifting/snippet.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 exercises/practice/grains/.approaches/bit-shifting/snippet.txt diff --git a/exercises/practice/grains/.approaches/bit-shifting/snippet.txt b/exercises/practice/grains/.approaches/bit-shifting/snippet.txt new file mode 100644 index 0000000000..28566f1173 --- /dev/null +++ b/exercises/practice/grains/.approaches/bit-shifting/snippet.txt @@ -0,0 +1,8 @@ +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + return 1 << number - 1 + + +def total(): + return (1 << 64) - 1 From f8f67aa44d6341c9ceeaf4515a81f46c3b79cf5d Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:17:15 -0600 Subject: [PATCH 066/826] Create snippet.txt --- .../grains/.approaches/exponentiation/snippet.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 exercises/practice/grains/.approaches/exponentiation/snippet.txt diff --git a/exercises/practice/grains/.approaches/exponentiation/snippet.txt b/exercises/practice/grains/.approaches/exponentiation/snippet.txt new file mode 100644 index 0000000000..483206302d --- /dev/null +++ b/exercises/practice/grains/.approaches/exponentiation/snippet.txt @@ -0,0 +1,8 @@ +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + return 2 ** (number - 1) + + +def total(): + return 2 ** 64 - 1 From 6a63153300a317baa6911d1570350bd5bd3df60f Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:17:51 -0600 Subject: [PATCH 067/826] Create snippet.txt --- exercises/practice/grains/.approaches/pow/snippet.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 exercises/practice/grains/.approaches/pow/snippet.txt diff --git a/exercises/practice/grains/.approaches/pow/snippet.txt b/exercises/practice/grains/.approaches/pow/snippet.txt new file mode 100644 index 0000000000..f8742af406 --- /dev/null +++ b/exercises/practice/grains/.approaches/pow/snippet.txt @@ -0,0 +1,8 @@ +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + return pow(2, number - 1) + + +def total(): + return pow(2, 64) - 1 From bc2490b183a3583a805b2b6a137720a1f31e0318 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:30:56 -0600 Subject: [PATCH 068/826] Create content.md --- .../.approaches/bit-shifting/content.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 exercises/practice/grains/.approaches/bit-shifting/content.md diff --git a/exercises/practice/grains/.approaches/bit-shifting/content.md b/exercises/practice/grains/.approaches/bit-shifting/content.md new file mode 100644 index 0000000000..409c4ffba0 --- /dev/null +++ b/exercises/practice/grains/.approaches/bit-shifting/content.md @@ -0,0 +1,50 @@ +# Bit-shifting + +```python +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + + return 1 << number - 1 + + +def total(): + return (1 << 64) - 1 + +``` + +Instead of using math for calculation, you can set a bit in the correct position for the number of grains on a square. + +To understand how this works, consider just two squares that are represented in binary bits as `00`. + +You use the [left-shift operator][left-shift-operator] to set `1` at the position needed to make the correct decimal value. +- To set the one grain on Square One you shift `1` for `0` positions to the left. +So, if `number` is `1` for square One, you subtract `number` by `1` to get `0`, which will not move it any positions to the left. +The result is binary `01`, which is decimal `1`. +- To set the two grains on Square Two you shift `1` for `1` position to the left. +So, if `number` is `2` for square Two, you subtract `number` by `1` to get `1`, which will move it `1` position to the left. +The result is binary `10`, which is decimal `2`. + +| Square | Shift Left By | Binary Value | Decimal Value | +| ------- | ------------- | ------------ | ------------- | +| 1 | 0 | 0001 | 1 | +| 2 | 1 | 0010 | 2 | +| 3 | 2 | 0100 | 4 | +| 4 | 3 | 1000 | 8 | + +For `total` we want all of the 64 bits set to `1` to get the sum of grains on all sixty-four squares. +The easy way to do this is to set the 65th bit to `1` and then subtract `1`. +To go back to our two-square example, if we can grow to three squares, then we can shift `1` two positions to the left for binary `100`, +which is decimal `4`. +By subtracting `1` we get `3`, which is the total amount of grains on the two squares. + +| Square | Binary Value | Decimal Value | +| ------- | ------------ | ------------- | +| 3 | 0100 | 4 | + +| Square | Sum Binary Value | Sum Decimal Value | +| ------- | ---------------- | ----------------- | +| 1 | 0001 | 1 | +| 2 | 0011 | 3 | + +[left-shift-operator]: https://realpython.com/python-bitwise-operators/#left-shift From 08460bbbec17166a7f61618e8bb56167fb80e462 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:35:43 -0600 Subject: [PATCH 069/826] Create content.md --- .../.approaches/exponentiation/content.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 exercises/practice/grains/.approaches/exponentiation/content.md diff --git a/exercises/practice/grains/.approaches/exponentiation/content.md b/exercises/practice/grains/.approaches/exponentiation/content.md new file mode 100644 index 0000000000..95e65ea2df --- /dev/null +++ b/exercises/practice/grains/.approaches/exponentiation/content.md @@ -0,0 +1,33 @@ +## Exponentiation + +```python +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + + return 2 ** (number - 1) + + +def total(): + return 2 ** 64 - 1 + +``` + +Python uses the exponential operator (`**`) to raise a number by a certain exponent. + +[Exponentiation][exponentiation] is nicely suited to the problem, since we start with one grain and keep doubling the number of grains on each successive square. +`1` grain is `2 ** 0`, `2` grains is `2 ** 1`, `4` is `2 ** 2`, and so on. + +So, to get the right exponent, we subtract `1` from the square `number`. + +The easiest way to get `total` is to get the value for an imaginary 65th square, +and then subtract `1` from it. +To understand how that works, consider a board that has only two squares. +If we could grow the board to three squares, then we could get the number of grains on the imaginary third square, +which would be `4.` +You could then subtract `4` by `1` to get `3`, which is the number of grains on the first square (`1`) and the second square (`2`). +Remembering that the exponent must be one less than the square you want, +you can call `2 ** 64` to get the number of grains on the imaginary 65th square. +Subtracting that value by `1` gives the values on all `64` squares. + +[exponentiation]: https://www.codingem.com/python-exponent-maths/ From 3b7ac87c403dde35e9b5778c86edb069d66166a3 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 16 Nov 2022 05:41:42 -0600 Subject: [PATCH 070/826] Create content.md --- .../grains/.approaches/pow/content.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 exercises/practice/grains/.approaches/pow/content.md diff --git a/exercises/practice/grains/.approaches/pow/content.md b/exercises/practice/grains/.approaches/pow/content.md new file mode 100644 index 0000000000..5a3dc1ee99 --- /dev/null +++ b/exercises/practice/grains/.approaches/pow/content.md @@ -0,0 +1,31 @@ +## `pow` + +```python +def square(number): + if number < 1 or number > 64: + raise ValueError('square must be between 1 and 64') + + return pow(2, number - 1) + + +def total(): + return pow(2, 64) - 1 + +``` + +[`pow`][pow] is nicely suited to the problem, since we start with one grain and keep doubling the number of grains on each successive square. +`1` grain is `pow(2, 0)`, `2` grains is `pow(2, 1)`, `4` is `pow(2, 2)`, and so on. + +So, to get the right exponent, we subtract `1` from the square `number`. + +The easiest way to get `total` is to use `pow` to get the value for an imaginary 65th square, +and then subtract `1` from it. +To understand how that works, consider a board that has only two squares. +If we could grow the board to three squares, then we could get the number of grains on the imaginary third square, +which would be `4.` +You could then subtract `4` by `1` to get `3`, which is the number of grains on the first square (`1`) and the second square (`2`). +Remembering that the exponent must be one less than the square you want, +you can call `pow(2, 64)` to get the number of grains on the imaginary 65th square. +Subtracting that value by `1` gives the values on all `64` squares. + +[pow]: https://docs.python.org/3/library/functions.html#pow From 9f7befe39cd81564a69f6b47980fd9d4654853fe Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 29 Nov 2022 08:48:45 +0100 Subject: [PATCH 071/826] Fail closed on pause contributions workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the original workflow, I had `catch(err) => false`. But during code review the question was raised: What happens if we get rate limited and all the requests from the API fail? (E.g. if we do a script and automatically create PRs across all of Exercism, which is totally a thing). We were like: ooh, that would suck, wouldn’t it? It would be better if we occasionally had to deal with manually closing a PR. But here’s the kicker. The API response has no body. It’s either 204 or 404, where 404 is perceived as… an error. So I changed it, and (importantly) forgot to test the script one final time. So here is a version that will actually work. (I tested.) --- .github/workflows/pause-community-contributions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pause-community-contributions.yml b/.github/workflows/pause-community-contributions.yml index 85d18d1c5d..16b1ca555d 100644 --- a/.github/workflows/pause-community-contributions.yml +++ b/.github/workflows/pause-community-contributions.yml @@ -30,7 +30,7 @@ jobs: org: context.repo.owner, username: context.actor, }).then(response => response.status == 204) - .catch(err => true); + .catch(err => false); - name: Comment if: steps.is-organization-member.outputs.result == 'false' uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 From 5f2f91ffac09649090a3257d4c39f8968b80d563 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 05:35:11 -0600 Subject: [PATCH 072/826] Create config.json --- .../practice/pangram/.approaches/config.json | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/config.json diff --git a/exercises/practice/pangram/.approaches/config.json b/exercises/practice/pangram/.approaches/config.json new file mode 100644 index 0000000000..550a3b5e11 --- /dev/null +++ b/exercises/practice/pangram/.approaches/config.json @@ -0,0 +1,35 @@ +{ + "introduction": { + "authors": ["bobahop"] + }, + "approaches": [ + { + "uuid": "999c333a-2516-4d91-9a8f-7cbc39e56914", + "slug": "all", + "title": "all on lower case", + "blurb": "Use all on lowercased letters.", + "authors": ["bobahop"] + }, + { + "uuid": "57720fea-8fe6-44c6-80b6-b8f356aa3008", + "slug": "set-issubset", + "title": "set with issubset", + "blurb": "Use set with issubset.", + "authors": ["bobahop"] + }, + { + "uuid": "2743b5c4-fbd1-4a73-ae39-d4d275313494", + "slug": "set-len", + "title": "set with len", + "blurb": "Use set with len.", + "authors": ["bobahop"] + }, + { + "uuid": "0a6d1bbf-6d60-4489-b8d9-b8375894628b", + "slug": "bitfield", + "title": "Bit field", + "blurb": "Use a bit field to keep track of used letters.", + "authors": ["bobahop"] + } + ] +} From c7a57105b70b0f7f272c6933096c8429140afd5d Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 05:36:38 -0600 Subject: [PATCH 073/826] Create config.json --- exercises/practice/pangram/.articles/config.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 exercises/practice/pangram/.articles/config.json diff --git a/exercises/practice/pangram/.articles/config.json b/exercises/practice/pangram/.articles/config.json new file mode 100644 index 0000000000..b7de79a678 --- /dev/null +++ b/exercises/practice/pangram/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "f832ad8a-09dc-4929-9e46-d3e7c286a063", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach to determining a pangram.", + "authors": ["bobahop"] + } + ] +} From 5d05960ff30affff08c8ab26f1e29cb323dbcc86 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 05:38:49 -0600 Subject: [PATCH 074/826] Create snippet.md --- .../practice/pangram/.articles/performance/snippet.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 exercises/practice/pangram/.articles/performance/snippet.md diff --git a/exercises/practice/pangram/.articles/performance/snippet.md b/exercises/practice/pangram/.articles/performance/snippet.md new file mode 100644 index 0000000000..0509fbee53 --- /dev/null +++ b/exercises/practice/pangram/.articles/performance/snippet.md @@ -0,0 +1,7 @@ +``` +all: 1.505466179997893e-05 +all: 1.6063886400021147e-05 // with sentence.casefold() +set: 1.950172399985604e-06 +len: 3.7158977999933994e-06 +bit: 8.75982620002469e-06 +``` From 54c76d67b2022baf50bf2c0cc029103fda943f39 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 05:39:40 -0600 Subject: [PATCH 075/826] Create Benchmark.py --- .../.articles/performance/code/Benchmark.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 exercises/practice/pangram/.articles/performance/code/Benchmark.py diff --git a/exercises/practice/pangram/.articles/performance/code/Benchmark.py b/exercises/practice/pangram/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..6ab5c8b1cf --- /dev/null +++ b/exercises/practice/pangram/.articles/performance/code/Benchmark.py @@ -0,0 +1,54 @@ +import timeit + +loops = 1_000_000 + +val = timeit.timeit("""is_pangram("Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.")""", + """ +from string import ascii_lowercase +def is_pangram(sentence): + return all(letter in sentence.lower() for letter in ascii_lowercase) + +""", number=loops) / loops + +print(f"all: {val}") + +val = timeit.timeit("""is_pangram("Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.")""", + """ +from string import ascii_lowercase + +ALPHABET = set(ascii_lowercase) + +def is_pangram(string): + return ALPHABET.issubset(string.lower()) + +""", number=loops) / loops + +print(f"set: {val}") + +val = timeit.timeit("""is_pangram("Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.")""", + """ +def is_pangram(sentence): + return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) == 26 + +""", number=loops) / loops + +print(f"len: {val}") + +val = timeit.timeit("""is_pangram("Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.")""", + """ +A_LCASE = 97; +A_UCASE = 65; +ALL_26_BITS_SET = 67108863; + +def is_pangram(sentence): + letter_flags = 0 + for letter in sentence: + if letter >= 'a' and letter <= 'z': + letter_flags |= 1 << (ord(letter) - A_LCASE) + elif letter >= 'A' and letter <= 'Z': + letter_flags |= 1 << (ord(letter) - A_UCASE) + return letter_flags == ALL_26_BITS_SET + +""", number=loops) / loops + +print(f"bit: {val}") From fa542ca742ef4050822d396470dc69f2e7c989a7 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 05:53:11 -0600 Subject: [PATCH 076/826] Create content.md --- .../pangram/.articles/performance/content.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 exercises/practice/pangram/.articles/performance/content.md diff --git a/exercises/practice/pangram/.articles/performance/content.md b/exercises/practice/pangram/.articles/performance/content.md new file mode 100644 index 0000000000..4f05a4157e --- /dev/null +++ b/exercises/practice/pangram/.articles/performance/content.md @@ -0,0 +1,37 @@ +# Performance + +In this approach, we'll find out how to most efficiently determine if a string is a Pangram in Python. + +The [approaches page][approaches] lists three idiomatic approaches to this exercise: + +1. [Using `all` on lowercased letters][approach-all] +2. [Using `set` with `issubset`][approach-set-issubset] +3. [Using `set` with `len`][approach-set-len] + +For our performance investigation, we'll also include a fourth approach that [uses a bit field to keep track of used letters][approach-bitfield]. + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. + +``` +all: 1.505466179997893e-05 +all: 1.6063886400021147e-05 // with sentence.casefold() +set: 1.950172399985604e-06 +len: 3.7158977999933994e-06 +bit: 8.75982620002469e-06 +``` + +- The `set` `len` approach is not as fast as the `set` `issubset` approach. +- The `all` approach is slower than either `set` approach. +Using `casefold` was slower than using `lower`. +- Although the bit field approach may be faster in other languages, it is significantly slower in Python. +It is faster than the `all` approach, but much slower than either `set` approach. + +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/pangram/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html +[approaches]: https://exercism.org/tracks/python/exercises/pangram/approaches +[approach-all]: https://exercism.org/tracks/python/exercises/pangram/approaches/all +[approach-set-issubset]: https://exercism.org/tracks/python/exercises/pangram/approaches/set-issubset +[approach-set-len]: https://exercism.org/tracks/python/exercises/pangram/approaches/set-len +[approach-bitfield]: https://exercism.org/tracks/python/exercises/pangram/approaches/bitfield From 9eb76a10f328d0673b6541559558fb52b9bb7ce1 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 05:58:12 -0600 Subject: [PATCH 077/826] Update Benchmark.py --- .../practice/pangram/.articles/performance/code/Benchmark.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/pangram/.articles/performance/code/Benchmark.py b/exercises/practice/pangram/.articles/performance/code/Benchmark.py index 6ab5c8b1cf..729cc967dc 100644 --- a/exercises/practice/pangram/.articles/performance/code/Benchmark.py +++ b/exercises/practice/pangram/.articles/performance/code/Benchmark.py @@ -18,8 +18,8 @@ def is_pangram(sentence): ALPHABET = set(ascii_lowercase) -def is_pangram(string): - return ALPHABET.issubset(string.lower()) +def is_pangram(sentence): + return ALPHABET.issubset(sentence.lower()) """, number=loops) / loops From 8cfcb6552660be8775d5d1249e52d5c5f44f9dc2 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:19:48 -0600 Subject: [PATCH 078/826] Create introduction.md --- .../pangram/.approaches/introduction.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/introduction.md diff --git a/exercises/practice/pangram/.approaches/introduction.md b/exercises/practice/pangram/.approaches/introduction.md new file mode 100644 index 0000000000..9536fe63d5 --- /dev/null +++ b/exercises/practice/pangram/.approaches/introduction.md @@ -0,0 +1,71 @@ +# Introduction + +There are various idomatic approaches to Pangram. +You can use the `all` method on the `ascii_lowercase` letters with the lowercased letters of the `sentence`. +You can see if the `set` of the alphabet `issubset` of a `set` of the lowercased `sentence`. +Or you can see if the `set` `len` of the lowercased `sentence` filtered to just ASCII letters is `26`. + +## General guidance + +The key to solving Pangram is determining if all of the letters in the alphabet are in the `sentence` being tested. +The occurrence of either the letter `a` or the letter `A` would count as the same letter. + +## Approach: `all` on lowercased letters + +```python +from string import ascii_lowercase + + +def is_pangram(sentence): + return all(letter in sentence.lower() for letter in ascii_lowercase) + +``` + +For more information, check the [`all` approach][approach-all]. + +## Approach: `set` with `issubset` on lowercased characters + +```python +from string import ascii_lowercase + +ALPHABET = set(ascii_lowercase) + + +def is_pangram(sentence): + return ALPHABET.issubset(sentence.lower()) + +``` + +For more information, check the [`set` with `issubset` approach][approach-set-issubset]. + +## Approach: `set` with `len` on lowercased characters + +```python +def is_pangram(sentence): + return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) \ + == 26 + +``` + +For more information, check the [`set` with `len` approach][approach-set-len]. + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +### Other approach: Bit field + +Another approach can use a bit field to keep track of used letters. +For more information, check the [Bit field approach][approach-bitfield]. + +## Which approach to use? + +The fastest is the `set` `issubset` approach. + +To compare performance of the approaches, check the [Performance article][article-performance]. + +[approach-all]: https://exercism.org/tracks/python/exercises/pangram/approaches/all +[approach-set-issubset]: https://exercism.org/tracks/python/exercises/pangram/approaches/set-issubset +[approach-set-len]: https://exercism.org/tracks/python/exercises/pangram/approaches/set-len +[approach-bitfield]: https://exercism.org/tracks/python/exercises/pangram/approaches/bitfield +[article-performance]: https://exercism.org/tracks/python/exercises/pangram/articles/performance From 9b69187dbfeba9dee0eeabb758449d72f7217261 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:24:03 -0600 Subject: [PATCH 079/826] Create snippet.txt --- exercises/practice/pangram/.approaches/all/snippet.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/all/snippet.txt diff --git a/exercises/practice/pangram/.approaches/all/snippet.txt b/exercises/practice/pangram/.approaches/all/snippet.txt new file mode 100644 index 0000000000..7e40ca76aa --- /dev/null +++ b/exercises/practice/pangram/.approaches/all/snippet.txt @@ -0,0 +1,5 @@ +from string import ascii_lowercase + + +def is_pangram(sentence): + return all(letter in sentence.lower() for letter in ascii_lowercase) From 6a09dd9a83ab1f67b200c8f2647dae48342687c7 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:24:49 -0600 Subject: [PATCH 080/826] Create snippet.txt --- .../practice/pangram/.approaches/set-issubset/snippet.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/set-issubset/snippet.txt diff --git a/exercises/practice/pangram/.approaches/set-issubset/snippet.txt b/exercises/practice/pangram/.approaches/set-issubset/snippet.txt new file mode 100644 index 0000000000..7e5e9272cc --- /dev/null +++ b/exercises/practice/pangram/.approaches/set-issubset/snippet.txt @@ -0,0 +1,7 @@ +from string import ascii_lowercase + +ALPHABET = set(ascii_lowercase) + + +def is_pangram(sentence): + return ALPHABET.issubset(sentence.lower()) From f48f213d207d44ac79a18887d0823f131d09ee85 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:25:24 -0600 Subject: [PATCH 081/826] Create snippet.txt --- exercises/practice/pangram/.approaches/set-len/snippet.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/set-len/snippet.txt diff --git a/exercises/practice/pangram/.approaches/set-len/snippet.txt b/exercises/practice/pangram/.approaches/set-len/snippet.txt new file mode 100644 index 0000000000..9a6a6d537b --- /dev/null +++ b/exercises/practice/pangram/.approaches/set-len/snippet.txt @@ -0,0 +1,3 @@ +def is_pangram(sentence): + return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) \ + == 26 From 8fce5b9028e8c048f11ffa88649eab3edff74b2f Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:26:47 -0600 Subject: [PATCH 082/826] Create snippet.txt --- .../practice/pangram/.approaches/bitfield/snippet.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/bitfield/snippet.txt diff --git a/exercises/practice/pangram/.approaches/bitfield/snippet.txt b/exercises/practice/pangram/.approaches/bitfield/snippet.txt new file mode 100644 index 0000000000..991e4568d0 --- /dev/null +++ b/exercises/practice/pangram/.approaches/bitfield/snippet.txt @@ -0,0 +1,8 @@ +def is_pangram(sentence): + letter_flags = 0 + for letter in sentence: + if letter >= 'a' and letter <= 'z': + letter_flags |= 1 << ord(letter) - A_LCASE + elif letter >= 'A' and letter <= 'Z': + letter_flags |= 1 << ord(letter) - A_UCASE + return letter_flags == ALL_26_BITS_SET From 9527ee0a54be0b61d017cd261a870ac0d6f7326f Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:34:21 -0600 Subject: [PATCH 083/826] Create content.md --- .../pangram/.approaches/all/content.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/all/content.md diff --git a/exercises/practice/pangram/.approaches/all/content.md b/exercises/practice/pangram/.approaches/all/content.md new file mode 100644 index 0000000000..7aace376a6 --- /dev/null +++ b/exercises/practice/pangram/.approaches/all/content.md @@ -0,0 +1,20 @@ +# `all` on lowercased letters + +```python +from string import ascii_lowercase + + +def is_pangram(sentence): + return all(letter in sentence.lower() for letter in ascii_lowercase) + +``` + +- This begins by importing all of the [ascii_lowercase][ascii-lowercase] letters. +- It lowercases the input by using the [lower][lower] method. +- It then checks if all letters in the lowercase alphabet are contained in the lowercased `sentence`, +using the [`all`][all] function. +- If all of the letters in the alphabet are contained in the `sentence`, then the function will return `True`. + +[ascii-lowercase]: https://docs.python.org/3/library/string.html#string.ascii_lowercase +[lower]: https://docs.python.org/3/library/stdtypes.html?#str.lower +[all]: https://docs.python.org/3/library/functions.html#all From b371a18abeb6d6b80b59c3363643273225b25362 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:42:05 -0600 Subject: [PATCH 084/826] Create content.md --- .../.approaches/set-issubset/content.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/set-issubset/content.md diff --git a/exercises/practice/pangram/.approaches/set-issubset/content.md b/exercises/practice/pangram/.approaches/set-issubset/content.md new file mode 100644 index 0000000000..34d012b246 --- /dev/null +++ b/exercises/practice/pangram/.approaches/set-issubset/content.md @@ -0,0 +1,24 @@ +# `HashSet` with `is_subset` + +```python +from string import ascii_lowercase + +ALPHABET = set(ascii_lowercase) + + +def is_pangram(sentence): + return ALPHABET.issubset(sentence.lower()) + +``` + +In this approach a [set][set] is made from the [ascii_lowercase][ascii-lowercase] letters, +and another `set` is made from the [lower][lower]cased letters in the `sentence`. + +The function returns if the alphabet `set` [issubset][issubset] of the `sentence` `set`. +If all of the letters in the alphabet are a subset of the letters in the `sentence`, +then the function will return `True`. + +[set]: https://docs.python.org/3/library/stdtypes.html?#set +[ascii-lowercase]: https://docs.python.org/3/library/string.html#string.ascii_lowercase +[lower]: https://docs.python.org/3/library/stdtypes.html?#str.lower +[issubset]: https://docs.python.org/3/library/stdtypes.html?highlight=issubset#frozenset.issubset From c661291db2a80bc1393cacfdfa95c23dafa7c326 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:59:03 -0600 Subject: [PATCH 085/826] Create content.md --- .../pangram/.approaches/set-len/content.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/set-len/content.md diff --git a/exercises/practice/pangram/.approaches/set-len/content.md b/exercises/practice/pangram/.approaches/set-len/content.md new file mode 100644 index 0000000000..645a47674a --- /dev/null +++ b/exercises/practice/pangram/.approaches/set-len/content.md @@ -0,0 +1,21 @@ +# `set` with `len` + +```python +def is_pangram(sentence): + return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) \ + == 26 + +``` + +- This approach first makes a [set][set] from the [`lower`][lower]cased characters of the `sentence`. +- The characters in the `set`are then iterated in a [list comprehension][list-comprehension]. +- The characters are filtered by an `if` [`isalpha()`][isalpha] statement, so that only alphabetic characters make it into the list. +- The function returns if the [`len`][len] of the [`list`][list] is `26`. +If the number of unique letters in the `set` is equal to the `26` letters in the alphabet, then the function will return `True`. + +[lower]: https://docs.python.org/3/library/stdtypes.html?#str.lower +[set]: https://docs.python.org/3/library/stdtypes.html?#set +[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[isalpha]: https://docs.python.org/3/library/stdtypes.html?highlight=isalpha#str.isalpha +[len]: https://docs.python.org/3/library/functions.html?#len +[list]: https://docs.python.org/3/library/stdtypes.html?#list From be737116c0f7a7dd95f1eb73e65387cf3819830d Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 06:59:51 -0600 Subject: [PATCH 086/826] Update content.md --- exercises/practice/pangram/.approaches/set-issubset/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/pangram/.approaches/set-issubset/content.md b/exercises/practice/pangram/.approaches/set-issubset/content.md index 34d012b246..1c43c637dd 100644 --- a/exercises/practice/pangram/.approaches/set-issubset/content.md +++ b/exercises/practice/pangram/.approaches/set-issubset/content.md @@ -12,7 +12,7 @@ def is_pangram(sentence): ``` In this approach a [set][set] is made from the [ascii_lowercase][ascii-lowercase] letters, -and another `set` is made from the [lower][lower]cased letters in the `sentence`. +and another `set` is made from the [`lower`][lower]cased letters in the `sentence`. The function returns if the alphabet `set` [issubset][issubset] of the `sentence` `set`. If all of the letters in the alphabet are a subset of the letters in the `sentence`, From 8d2697260fd26a15dbe9b69e6b65a1b72961d7b2 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 07:13:03 -0600 Subject: [PATCH 087/826] Create content.md --- .../pangram/.approaches/bitfield/content.md | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 exercises/practice/pangram/.approaches/bitfield/content.md diff --git a/exercises/practice/pangram/.approaches/bitfield/content.md b/exercises/practice/pangram/.approaches/bitfield/content.md new file mode 100644 index 0000000000..20e460fea0 --- /dev/null +++ b/exercises/practice/pangram/.approaches/bitfield/content.md @@ -0,0 +1,62 @@ +# Bit field + +```python +A_LCASE = 97 +A_UCASE = 65 +ALL_26_BITS_SET = 67108863 + + +def is_pangram(sentence): + letter_flags = 0 + for letter in sentence: + if letter >= 'a' and letter <= 'z': + letter_flags |= 1 << ord(letter) - A_LCASE + elif letter >= 'A' and letter <= 'Z': + letter_flags |= 1 << ord(letter) - A_UCASE + return letter_flags == ALL_26_BITS_SET + +``` + +This solution uses the [ASCII][ascii] value of the letter to set the corresponding bit position. +First, some [constant][const] values are set. + +```exercism/note +Python doesn't _enforce_ having real constant values, +but using all uppercase letters is the naming convention for a Python constant. +It indicates that the value is not intended to be changed. +``` + +These values will be used for readability in the body of the `is_pangram` function. +The ASCII value for `a` is `97`. +The ASCII value for `A` is `65`. +The value for all of the rightmost `26` bits being set is `67108863`. + +- The [`for` loop][for-loop] loops through the characters of the `sentence`. +- Each letter is tested for being `a` through `z` or `A` through `Z`. +- If the lowercased letter is subtracted by `a`, then `a` will result in `0`, because `97` minus `97` equals `0`. +`z` would result in `25`, because `122` minus `97` equals `25`. +So `a` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `z` would have `1` shifted left 25 places. +- If the uppercased letter is subtracted by `A`, then `A` will result in `0`, because `65` minus `65` equals `0`. +`Z` would result in `25`, because `90` minus `65` equals `25`. +So `A` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `Z` would have `1` shifted left 25 places. + +In that way, both a lower-cased `z` and an upper-cased `Z` can share the same position in the bit field. + +So, for a thirty-two bit integer, if the values for `a` and `Z` were both set, the bits would look like + +``` + zyxwvutsrqponmlkjihgfedcba +00000010000000000000000000000001 +``` + +We can use the [bitwise OR operator][or] to set the bit. +After the loop completes, the function returns `True` if the `letter_flags` value is the same value as when all of the rightmost `26` bits are set, +which is `67108863`. + +Although this approach is usually very fast in some other languages, it is comparatively slow in Python. + +[ascii]: https://www.asciitable.com/ +[const]: https://realpython.com/python-constants/ +[for-loop]: https://wiki.python.org/moin/ForLoop +[shift-left]: https://realpython.com/python-bitwise-operators/#left-shift +[or]: https://realpython.com/python-bitwise-operators/#bitwise-or From f43f8cae06f23b7f0aa9f22fcf86929ce18858e8 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 08:12:03 -0600 Subject: [PATCH 088/826] Update content.md --- .../practice/pangram/.approaches/all/content.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/exercises/practice/pangram/.approaches/all/content.md b/exercises/practice/pangram/.approaches/all/content.md index 7aace376a6..ce09af36d5 100644 --- a/exercises/practice/pangram/.approaches/all/content.md +++ b/exercises/practice/pangram/.approaches/all/content.md @@ -1,4 +1,4 @@ -# `all` on lowercased letters +# `all()` on lowercased letters ```python from string import ascii_lowercase @@ -10,11 +10,19 @@ def is_pangram(sentence): ``` - This begins by importing all of the [ascii_lowercase][ascii-lowercase] letters. -- It lowercases the input by using the [lower][lower] method. +- It lowercases the input by using the [lower()][lower] method. - It then checks if all letters in the lowercase alphabet are contained in the lowercased `sentence`, -using the [`all`][all] function. +using the [`all()`][all] function. - If all of the letters in the alphabet are contained in the `sentence`, then the function will return `True`. +```exercism/note +Instead of `lower()`, the [`casefold`](https://docs.python.org/3/library/stdtypes.html#str.casefold) +method could be used to lowercase the letters. +`casefold()` differs from `lower()` in lowercasing certain Unicode characters. +At the time of writing, those differences are not of concern to this exercise. +Also, `casefold()` benched slower than `lower()`. +``` + [ascii-lowercase]: https://docs.python.org/3/library/string.html#string.ascii_lowercase [lower]: https://docs.python.org/3/library/stdtypes.html?#str.lower [all]: https://docs.python.org/3/library/functions.html#all From 39e93e082182c1f53f4acff3ea58d28ebb3bdae3 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 08:12:56 -0600 Subject: [PATCH 089/826] Update content.md --- exercises/practice/pangram/.approaches/set-issubset/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/pangram/.approaches/set-issubset/content.md b/exercises/practice/pangram/.approaches/set-issubset/content.md index 1c43c637dd..c87dd604ef 100644 --- a/exercises/practice/pangram/.approaches/set-issubset/content.md +++ b/exercises/practice/pangram/.approaches/set-issubset/content.md @@ -14,7 +14,7 @@ def is_pangram(sentence): In this approach a [set][set] is made from the [ascii_lowercase][ascii-lowercase] letters, and another `set` is made from the [`lower`][lower]cased letters in the `sentence`. -The function returns if the alphabet `set` [issubset][issubset] of the `sentence` `set`. +The function returns if the alphabet `set` [issubset()][issubset] of the `sentence` `set`. If all of the letters in the alphabet are a subset of the letters in the `sentence`, then the function will return `True`. From 2f8f1ec4952868a4ece001a62b5070a6cb8be370 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 08:14:55 -0600 Subject: [PATCH 090/826] Update introduction.md --- .../practice/pangram/.approaches/introduction.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/exercises/practice/pangram/.approaches/introduction.md b/exercises/practice/pangram/.approaches/introduction.md index 9536fe63d5..fec919313e 100644 --- a/exercises/practice/pangram/.approaches/introduction.md +++ b/exercises/practice/pangram/.approaches/introduction.md @@ -10,7 +10,7 @@ Or you can see if the `set` `len` of the lowercased `sentence` filtered to just The key to solving Pangram is determining if all of the letters in the alphabet are in the `sentence` being tested. The occurrence of either the letter `a` or the letter `A` would count as the same letter. -## Approach: `all` on lowercased letters +## Approach: `all()` on lowercased letters ```python from string import ascii_lowercase @@ -21,9 +21,9 @@ def is_pangram(sentence): ``` -For more information, check the [`all` approach][approach-all]. +For more information, check the [`all()` approach][approach-all]. -## Approach: `set` with `issubset` on lowercased characters +## Approach: `set` with `issubset()` on lowercased characters ```python from string import ascii_lowercase @@ -36,9 +36,9 @@ def is_pangram(sentence): ``` -For more information, check the [`set` with `issubset` approach][approach-set-issubset]. +For more information, check the [`set` with `issubset()` approach][approach-set-issubset]. -## Approach: `set` with `len` on lowercased characters +## Approach: `set` with `len()` on lowercased characters ```python def is_pangram(sentence): @@ -47,7 +47,7 @@ def is_pangram(sentence): ``` -For more information, check the [`set` with `len` approach][approach-set-len]. +For more information, check the [`set` with `len()` approach][approach-set-len]. ## Other approaches @@ -56,11 +56,11 @@ Besides the aforementioned, idiomatic approaches, you could also approach the ex ### Other approach: Bit field Another approach can use a bit field to keep track of used letters. -For more information, check the [Bit field approach][approach-bitfield]. +For more information, check the [bit field approach][approach-bitfield]. ## Which approach to use? -The fastest is the `set` `issubset` approach. +The fastest is the `set` `issubset()` approach. To compare performance of the approaches, check the [Performance article][article-performance]. From 6e307129c2322dbba556b7b256a82c9c643692f4 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 17 Nov 2022 08:16:35 -0600 Subject: [PATCH 091/826] Update content.md --- exercises/practice/pangram/.approaches/set-len/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/pangram/.approaches/set-len/content.md b/exercises/practice/pangram/.approaches/set-len/content.md index 645a47674a..e3f99fc756 100644 --- a/exercises/practice/pangram/.approaches/set-len/content.md +++ b/exercises/practice/pangram/.approaches/set-len/content.md @@ -1,4 +1,4 @@ -# `set` with `len` +# `set` with `len()` ```python def is_pangram(sentence): @@ -10,7 +10,7 @@ def is_pangram(sentence): - This approach first makes a [set][set] from the [`lower`][lower]cased characters of the `sentence`. - The characters in the `set`are then iterated in a [list comprehension][list-comprehension]. - The characters are filtered by an `if` [`isalpha()`][isalpha] statement, so that only alphabetic characters make it into the list. -- The function returns if the [`len`][len] of the [`list`][list] is `26`. +- The function returns if the [`len()`][len] of the [`list`][list] is `26`. If the number of unique letters in the `set` is equal to the `26` letters in the alphabet, then the function will return `True`. [lower]: https://docs.python.org/3/library/stdtypes.html?#str.lower From e84dd5abeac87616143e096a29a432ed5de238fd Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 06:55:09 -0600 Subject: [PATCH 092/826] Update introduction.md --- exercises/practice/pangram/.approaches/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/pangram/.approaches/introduction.md b/exercises/practice/pangram/.approaches/introduction.md index fec919313e..cf5538c015 100644 --- a/exercises/practice/pangram/.approaches/introduction.md +++ b/exercises/practice/pangram/.approaches/introduction.md @@ -1,9 +1,9 @@ # Introduction There are various idomatic approaches to Pangram. -You can use the `all` method on the `ascii_lowercase` letters with the lowercased letters of the `sentence`. -You can see if the `set` of the alphabet `issubset` of a `set` of the lowercased `sentence`. -Or you can see if the `set` `len` of the lowercased `sentence` filtered to just ASCII letters is `26`. +You can use the `all()` method on the `ascii_lowercase` letters with the lowercased letters of the `sentence`. +You can see if the `set` of the alphabet `issubset()` of a `set` of the lowercased `sentence`. +Or you can see if the `set` `len()` of the lowercased `sentence` filtered to just ASCII letters is `26`. ## General guidance From d98370b6772d0f5e3f45d18d3a70e26fc5725dda Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 06:57:08 -0600 Subject: [PATCH 093/826] Update content.md --- exercises/practice/pangram/.approaches/set-len/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/pangram/.approaches/set-len/content.md b/exercises/practice/pangram/.approaches/set-len/content.md index e3f99fc756..b647a01d49 100644 --- a/exercises/practice/pangram/.approaches/set-len/content.md +++ b/exercises/practice/pangram/.approaches/set-len/content.md @@ -10,7 +10,7 @@ def is_pangram(sentence): - This approach first makes a [set][set] from the [`lower`][lower]cased characters of the `sentence`. - The characters in the `set`are then iterated in a [list comprehension][list-comprehension]. - The characters are filtered by an `if` [`isalpha()`][isalpha] statement, so that only alphabetic characters make it into the list. -- The function returns if the [`len()`][len] of the [`list`][list] is `26`. +- The function returns whether the [`len()`][len] of the [`list`][list] is `26`. If the number of unique letters in the `set` is equal to the `26` letters in the alphabet, then the function will return `True`. [lower]: https://docs.python.org/3/library/stdtypes.html?#str.lower From 10aef0230f2e7cab0fe557a0bfeae0339b7d7da4 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 06:58:35 -0600 Subject: [PATCH 094/826] Update content.md --- .../pangram/.articles/performance/content.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/practice/pangram/.articles/performance/content.md b/exercises/practice/pangram/.articles/performance/content.md index 4f05a4157e..c5546e948b 100644 --- a/exercises/practice/pangram/.articles/performance/content.md +++ b/exercises/practice/pangram/.articles/performance/content.md @@ -4,9 +4,9 @@ In this approach, we'll find out how to most efficiently determine if a string i The [approaches page][approaches] lists three idiomatic approaches to this exercise: -1. [Using `all` on lowercased letters][approach-all] -2. [Using `set` with `issubset`][approach-set-issubset] -3. [Using `set` with `len`][approach-set-len] +1. [Using `all()` on lowercased letters][approach-all] +2. [Using `set` with `issubset()`][approach-set-issubset] +3. [Using `set` with `len()`][approach-set-len] For our performance investigation, we'll also include a fourth approach that [uses a bit field to keep track of used letters][approach-bitfield]. @@ -22,11 +22,11 @@ len: 3.7158977999933994e-06 bit: 8.75982620002469e-06 ``` -- The `set` `len` approach is not as fast as the `set` `issubset` approach. -- The `all` approach is slower than either `set` approach. +- The `set` `len()` approach is not as fast as the `set` `issubset()` approach. +- The `all()` approach is slower than either `set` approach. Using `casefold` was slower than using `lower`. - Although the bit field approach may be faster in other languages, it is significantly slower in Python. -It is faster than the `all` approach, but much slower than either `set` approach. +It is faster than the `all()` approach, but much slower than either `set` approach. [benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/pangram/.articles/performance/code/Benchmark.py [timeit]: https://docs.python.org/3/library/timeit.html From b32fb92ad483bb3fcbf2708088fae25a4e97e471 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 29 Nov 2022 17:47:59 -0800 Subject: [PATCH 095/826] Update exercises/practice/pangram/.approaches/set-issubset/content.md --- exercises/practice/pangram/.approaches/set-issubset/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/pangram/.approaches/set-issubset/content.md b/exercises/practice/pangram/.approaches/set-issubset/content.md index c87dd604ef..80e31444f0 100644 --- a/exercises/practice/pangram/.approaches/set-issubset/content.md +++ b/exercises/practice/pangram/.approaches/set-issubset/content.md @@ -1,4 +1,4 @@ -# `HashSet` with `is_subset` +# `set` with `is_subset` ```python from string import ascii_lowercase From 17d75187f8c0afa3bd4bac9125ed124683a261de Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 29 Nov 2022 17:12:58 -0800 Subject: [PATCH 096/826] Updated Contributing and readme for contribution pause. --- CONTRIBUTING.md | 264 ++++++++++++++++++++++-------------------------- README.md | 77 +++++++------- 2 files changed, 157 insertions(+), 184 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 816a44f6b9..691ca9f92f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,68 +3,66 @@

Contributing

-
-
+                          [![Discourse topics](https://img.shields.io/discourse/topics?color=8A08E6&label=Connect%20&labelColor=FFDF58&logo=Discourse&logoColor=8A08E6&server=https%3A%2F%2Fforum.exercism.org&style=social)](https://forum.exercism.org) +  [![Exercism_II](https://img.shields.io/badge/Exercism--Built-9101FF?logo=python&logoColor=FFDF58&labelColor=3D7AAB&label=Python%203.10%20Powered)](https://exercism.org) +  [![Exercism_III](https://img.shields.io/badge/PAUSED-C73D4E?labelColor=3D454D&label=Contributions)](https://exercism.org/blog/freeing-our-maintainers) +  [![Build Status](https://github.com/exercism/python/workflows/Exercises%20check/badge.svg)](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22) -Hi.  👋🏽  👋   **We are happy you are here.**  🎉🌟 - -Thank you so much for your interest in contributing! +
-**`exercsim/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. -This repo holds all the instructions, tests, code, & support files for Python *exercises* currently under development or implemented & available for students. +Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 - 🌟   Track exercises support Python `3.8`. - 🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.9`. + + +
+ -Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴  . Concept exercises are constrained to a small set of language or syntax features. Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory. +We 💛 💙   our community. +**`But our maintainers are not accepting community contributions at this time.`** +Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details. + +

- +**`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. +This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. -🌟🌟  If you have not already done so, please take a moment to read our [Code of Conduct][exercism-code-of-conduct]. 🌟🌟  -It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use], and [Pull Requests][prs]. +🌟   Track exercises support Python `3.7` - `3.10.6`. +Exceptions to this support are noted where they occur. + 🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.10.6`. -Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins] - -
- - -✨ 🦄  _**Want to jump directly into Exercism specifications & detail?**_ -     [Structure][exercism-track-structure] **|** [Tasks][exercism-tasks] **|** [Concepts][exercism-concepts] **|** [Concept Exercises][concept-exercises] **|** [Practice Exercises][practice-exercises] **|** [Presentation][exercise-presentation] -     [Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_ ✨   versions available in [contributing][website-contributing-section] on [exercism(dot)org][exercism-website]._) +Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴  . +Concept exercises are constrained to a small set of language or syntax features. +Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. +These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory.
## 🐛 **Did you find a bug?** -It is not uncommon to discover typos, confusing directions, or incorrect implementations of certain tests or code examples. Or you might have a great suggestion for a hint to aid students ( 💙  ), see optimizations for exemplar or test code, find missing test cases to add, or want to correct factual and/or logical errors. Or maybe you have a great idea for an exercise or feature (❗ ). +It is not uncommon to discover typos, confusing directions, or incorrect implementations of certain tests or code examples. Or you might have a great suggestion for a hint to aid students ( 💙  ), see optimizations for exemplar or test code, find missing test cases to add, or want to correct factual and/or logical errors. Or maybe you have a great idea 💡 for an exercise or feature ( 💙 ). _Our track is always a work in progress!_ 🌟🌟 -Please 📛 [ Open an issue ][open-an-issue]📛 , and let us know what you have found/suggest. +While contributions are paused, we ask that you [`open a thread in our community forum`](https://forum.exercism.org) to let us know what you have found/suggest.
-## 🚧 **Did you write a patch that fixes a bug?** -_Before you get started, please review [Pull Requests][prs]._ - +## 🚧 **Did you write a patch that fixes a bug?** - 💛 💙  **We Warmly Welcome Pull Requests that are:** +**`Our maintainers are not accepting community contributions at this time.`** +Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details. -             1️⃣     Small, contained fixes for typos/grammar/punctuation/code syntax on [one] exercise, -             2️⃣     Medium changes that have been agreed/discussed via a filed issue, -             3️⃣     Contributions from our [help wanted][help-wanted] issue list, -             4️⃣     Larger (_and previously agreed-upon_) contributions from recent & regular (_within the last 6 months_) contributors. +Once the pause ends, we will **happily** consider your PR. +Until that time, all PRs from the larger community will be **automatically closed** with a note. -When in doubt, 📛 [ Open an issue ][open-an-issue]📛 . We will happily discuss your proposed change. -🐍  _But we should talk before you take a whole lot of time or energy implementing anything._ +We're leaving the general contributing docs below for our long-term collaborators and maintainers.
- -

In General

+ +

In General


-- Please make sure to have a quick read-through of our Exercism [Pull Requests][prs] document before jumping in. 😅 - Maintainers are happy to review your work and help troubleshoot with you. 💛 💙  - Requests are reviewed as soon as is practical/possible. - (❗ ) Reviewers may be in a different timezone ⌚ , or tied up  🧶  with other tasks. @@ -78,62 +76,47 @@ When in doubt, 📛 [ Open an issue ][open-an-issue]📛 . We wil - creating a Pull Request making significant or breaking changes. - for changes across multiple exercises, even if they are typos or small. - anything that is going to require doing a lot of work (_on your part or the maintainers part_). -- Follow coding standards found in [PEP8][PEP8] (["For Humans" version here][pep8-for-humans]). -- All files should have a proper [EOL][EOL]. This means one carriage return at the end of the final line of text files. +- Follow coding standards found in [PEP8][pep8] (["For Humans" version here][pep8-for-humans]). +- All files should have a proper [EOL][eol]. This means one carriage return at the end of the final line of text files. - Otherwise, watch out  ⚠️  for trailing spaces, extra blank lines, extra spaces, and spaces in blank lines. -- Continuous Integration is going to run **a lot** of checks. Try to understand & fix any failures. +- Continuous Integration is going to run **a lot** of checks. Pay attention to failures & try to understand and fix them.
+
⚠️  Pre-Commit Checklist ⚠️
- - -
-
- - [ ]  Update & rebase your branch with any (recent) upstream changes. - - [ ]  Spell and grammar check all prose changes. - - [ ]  Run [Prettier](https://prettier.io/) on all markdown and JSON files. - - (_Optionally_) run [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) to help format your code. - - [ ]  Run [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) to check general code style standards. - - [ ]   Run [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc) to check extended code style standards. - - [ ]  Use pytest or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed `example.py`/`exemplar.py`files -  against their associated test files. - - [ ]  Similarly, use [pytest](https://docs.pytest.org/en/6.2.x/contents.html) or - the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed _**test**_ files. - - Check that tests **fail** properly, as well as succeed. -  (_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_). - - [ ]  Double-check all files for proper EOL. - - [ ]  [Regenerate](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#generating-practice-exercise-documents) exercise documents when you modified or created a `hints.md` file for a practice exercise. - - [ ]  [Regenerate the test file](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates) if you modified or created a `JinJa2` template file for a practice exercise. - - Run the generated test file result against its `example.py`. - - [ ]  Run [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint) if the track [config.json](https://github.com/exercism/docs/blob/main/building/tracks/config-json.md), or any other exercise `config.json` has been modified. - -
-
+1.  Run [`configlet-lint`][configlet-lint] if the track [config.json](config-json) has been modified. +2.  Run [Prettier][prettier] on all markdown files. +3.  (_Optionally_) run [yapf][yapf] ([_config file_][.style.yapf]) to help format your code, and give you a head start on making the linters happy. +4.  Run [flake8][flake8] ([_config file_][.flake8]) & [pylint][pylint] ([_config file_][pylintrc]) to ensure all Python code files conform to general code style standards. +5.  Run `test/check-exercises.py [EXERCISE]` to check if your test changes function correctly. +6.  Run the `example.py` or `exemplar.py` file against the exercise test file to ensure that it passes without error. +7.  If you modified or created a `hints.md` file for a practice exercise, [regenerate](#generating-practice-exercise-documents) it. + +
- -

Prose Writing Style and Standards

+
+ +

Prose Writing Style & Standards


-Non-code content (_exercise introductions & instructions, hints, concept write-ups, documentation etc._) should be written in [American English][american-english]. -We strive to watch [the words we use][the-words-that-we-use]. +Non-code content (_exercise introductions & instructions, hints, concept write-ups, documentation etc._) should be written in [American English][american-english]. We strive to watch [the words we use][the-words-that-we-use]. -When a word or phrase usage is contested/ambiguous, we default to what is best understood by our international community of learners, even if it "sounds a little weird" to a "native" American English speaker. +When a word or phrase usage is contested | ambiguous, we default to what is best understood by our international community of learners, even if it "sounds a little weird" to a "native" American English speaker. Our documents use [Markdown][markdown-language], with certain [alterations][exercism-markdown-widgets] & [additions][exercism-internal-linking]. Here is our full [Markdown Specification][exercism-markdown-specification].  📐 We format/lint our Markdown with [Prettier][prettier]. ✨ -
-

Coding Standards

+

Coding Standards


-1. We follow [PEP8][PEP8] (["For Humans" version here][pep8-for-humans]). +1. We follow [PEP8][pep8] (["For Humans" version here][pep8-for-humans]). In particular, we (mostly) follow the [Google flavor][google-coding-style] of PEP8. 2. We use [flake8][flake8] to help us format Python code nicely. Our `flake8` config file is [.flake8][.flake8] in the top level of this repo. @@ -153,7 +136,7 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer - **120 character per line limit** (_as opposed to the default limit of 79_) - Variable, function, and method names should be `lower_case_with_underscores` (_aka "snake case"_) - Classes should be named in `TitleCase` (_aka "camel case"_) -- **No single letter variable names** outside of a `lambda`. This includes loop variables and comprehensions. +- **No single letter variable names** outside of a `lambda`. This includes loop variables and comprehensions. - Refrain from putting `list`, `tuple`, `set`, or `dict` members on their own lines. Fit as many data members as can be easily read on one line, before wrapping to a second. - If a data structure spreads to more than one line and a break (_for clarity_) is needed, prefer breaking after the opening bracket. @@ -162,20 +145,20 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer - Use **`"""`** for docstrings. - Prefer [implicit line joining][implicit-line-joining] for long strings. - Prefer enclosing imports in **`()`**, and putting each on their own line when importing multiple methods. -- Two lines between `Classes`, one line between `functions`. Other vertical whitespace as needed to help readability. +- Two lines between `Classes`, one line between `functions`. Other vertical whitespace as needed to help readability. - Always use an **`EOL`** to end a file.
- Test File Style (concept exercises) + Test File Style (concept exercises)
- [Unittest.TestCase][unittest] syntax, with [PyTest][pytest] as a test runner. - We are transitioning to using more PyTest features/syntax, but are leaving `Unittest` syntax in place where possible. - Always check with a maintainer before introducing a PyTest feature into your tests. - Test **Classes** should be titled `Test`. **e.g.** `class CardGamesTest(unittest.TestCase):` -- Test method names should begin with `test_`. Try to make test case names descriptive but not too long. +- Test method names should begin with `test_`. Try to make test case names descriptive but not too long. - Favor [_parameterizing_][distinguishing-test-iterations] tests that only vary input data. Use [unittest.TestCase.subTest][subtest] for parameterization. - An [example from Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile]. - A second [example from Card Games][card-games-testfile]. @@ -184,20 +167,19 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer - Use [`enumerate()`][enumerate] where possible when indexes are needed. See [Card Games][card-games-testfile] for example usage. - Favor using names like `inputs`, `data`, `input_data`, `test_data`, or `test_case_data` for test inputs. - Favor using names like `results`, `expected`, `result_data`, `expected_data`, or `expected_results` for test outcomes. -- Favor putting the assert failure message outside of `self.assert()`. Name it `failure_msg`. See [Card Games][card-games-testfile] for example usage. -- Favor `f-strings` for dynamic failure messages. Please make your error messages as relevant and human-readable as possible. +- Favor putting the assert failure message outside of `self.assert()`. Name it `failure_msg`. See [Card Games][card-games-testfile] for example usage. +- Favor `f-strings` for dynamic failure messages. Please make your error messages as relevant and human-readable as possible. - We relate test cases to **task number** via a custom [PyTest Marker][pytestmark]. - These take the form of `@pytest.mark.task(taskno=)`. See [Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile] for an example. -- We prefer **test data files** when test inputs/outputs are verbose. +- We prefer **test data files** when test inputs/ouputs are verbose. - These should be named with `_data` or `_test_data` at the end of the filename, and saved alongside the test case file. - See the [Cater-Waiter][cater-waiter] exercise directory for an example of this setup. - **Test data files** need to be added under an `editor` key within [`config.json "files"`][exercise-config-json]. - Check with a maintainer if you have questions or issues, or need help with an exercise `config.json`. -- For new test files going forward, omit `if __name__ == "__main__": - unittest.main()`. +- For new test files going forward, omit `if __name__ == "__main__": unittest.main()`. - Lint with both `flake8` and `pylint`. - Both linters are known to toss false-positives for some testing patterns. - - Where necessary, deploy the [`#noqa`][flake8-noqa] or [`#pylint disable=`][pylint-disable-check] comments to suppress false-positive warnings. - See **line 16** of [Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile] test file for an example of an override. + - Where necessary, deploy the [`#noqa`][flake8-noqa] or [`#pylint disable=`][pylint-disable-check] comments to suppress false-positive warnings. - See **line 16** of [Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile] test file for an example of an override.

@@ -205,30 +187,28 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer If you have any questions or issues, don't hesitate to ask the maintainers -- they're always happy to help 💛 💙  Some of our code is old and does not (yet) conform to all these standards. -_**We know it, and trust us, we are working on fixing it.**_ But if you see  👀  something,  👄  say something. It will motivate us to fix it! 🌈 +_We know it, and trust us, we are working on fixing it._ But if you see  👀  something,  👄  say something. It'll motivate us to fix it! 🌈
-

Python Versions

+

Language Versions


-This track officially supports Python = `3.8` -The track `test runner`, `analyzer`, and `representer` run in docker on `python:3.9-slim`. +This track officially supports Python `3.7 - 3.10.6` for students completing exercises. +The track `test runner`, `analyzer`, and `representer` run in docker on `python:3.10.6-slim`. -- All exercises should be written for compatibility with Python = `3.8` or `3.9`. -- Version backward _incompatibility_ (*e.g* an exercise using a `3.8` or `3.9` **only** feature) should be clearly noted in any exercise hits, links, introductions or other notes. +Although the majority of test cases are written using `unittest.TestCase`, -- Here is an example of how the Python documentation handles [version-tagged  🏷 ][version-tagged-language-features] feature introduction. +- All exercises should be written for compatibility with Python `3.7` - `3.10.6`. +- Version backward _incompatibility_ (_e.g_ an exercise using features introduced in `3.8`, `3.9`, or `3.10`) should be clearly noted in any exercise hints, links, introductions or other notes. -- _Most_ exercises will work with Python `3.6+`, and _many_ are compatible with Python `2.7+`. - - Please do not change existing exercises to add new language features without consulting with a maintainer first. - - We  💛 💙  modern Python, but we _also_ want to avoid student confusion when it comes to which Python versions support brand-new features. +- Here is an example of how the Python documentation handles [version-tagged  🏷 ][version-tagged-language-features] feature introduction. -- All test suites and example solutions must work in all Python versions that we currently support. When in doubt about a feature, please check with maintainers. +- _Most_ exercises will work with Python `3.6+`, and _many_ are compatible with Python `2.7+`. Please do not change existing exercises to add new language features without consulting with a maintainer first. We  💛 💙  modern Python, but we _also_ want to avoid student confusion when it comes to which Python versions support brand-new features. -
+* All test suites and example solutions must work in all Python versions that we currently support. When in doubt about a feature, please check with maintainers.
@@ -237,22 +217,19 @@ The track `test runner`, `analyzer`, and `representer` run in docker on `python:
-- Each exercise must be self-contained. Please do not use or reference files that reside outside the given exercise directory. "Outside" files will not be included if a student fetches the exercise via the Command line Interface. +- Each exercise must be self-contained. Please do not use or reference files that reside outside the given exercise directory. "Outside" files will not be included if a student fetches the exercise via the CLI. + +- Each exercise/problem should include a complete test suite, an example/exemplar solution, and a stub file ready for student implementation. -- Each exercise/problem should include - - a complete test suite, - - an example/exemplar solution, - - a stub file ready for student implementation. +- For specifications, refer to [Concept Exercise Anatomy][concept-exercise-anatomy], or [Practice Exercise Anatomy][practice-exercise-anatomy] depending on which type of exercise you are contributing to. -- For specifications, refer to the links below, depending on which type of exercise you are contributing to. - - [Concept Exercise Anatomy][concept-exercise-anatomy] - - [Practice Exercise Anatomy][practice-exercise-anatomy] +- **Practice exercise**, descriptions and instructions come from a centralized, cross-track [problem specifications][problem-specifications] repository. -- **Practice exercise**, descriptions and instructions come from a centralized, cross-track [problem specifications][problem-specifications] repository. - Any updates or changes need to be proposed/approved in `problem-specifications` first. - - If Python-specific changes become necessary, they need to be appended to the canonical instructions by creating a `instructions.append.md` file in this (`exercism/Python`) repository. + - If Python-specific changes become necessary, they need to be appended to the canonical instructions by creating a `instructions.append.md` file in this (`exercism/Python`) repository. - Practice Exercise **Test Suits** for many practice exercises are similarly [auto-generated](#auto-generated-files) from data in [problem specifications][problem-specifications]. + - Any changes to them need to be proposed/discussed in the `problem-specifications` repository and approved by **3 track maintainers**, since changes could potentially affect many (_or all_) exercism language tracks. - If Python-specific test changes become necessary, they can be appended to the exercise `tests.toml` file. - 📛 [ **Please file an issue**][open-an-issue] 📛  and check with maintainers before adding any Python-specific tests. @@ -265,18 +242,18 @@ The track `test runner`, `analyzer`, and `representer` run in docker on `python:
- - [ ] `.docs/hints.md` - - [ ] `.docs/instructions.md` - - [ ] `.docs/introduction.md` - - [ ] `.meta/config.json` - - [ ] `.meta/design.md` - - [ ] `.meta/exemplar.py` (_exemplar solution_) - - [ ] `_test.py` (_test file_) - - [ ] `.py` (_stub file_) - - [ ] `concepts/../introduction.md` - - [ ] `concepts/../about.md` - - [ ] `concepts/../links.json` - - [ ] `concepts/../.meta/config.json` + - [ ] `.docs/hints.md` + - [ ] `.docs/instructions.md` + - [ ] `.docs/introduction.md` + - [ ] `.meta/config.json` + - [ ] `.meta/design.md` + - [ ] `.meta/exemplar.py` (_exemplar solution_) + - [ ] `_test.py` (_test file_) + - [ ] `.py` (_stub file_) + - [ ] `concepts/../introduction.md` + - [ ] `concepts/../about.md` + - [ ] `concepts/../links.json` + - [ ] `concepts/../.meta/config.json` @@ -286,50 +263,46 @@ The track `test runner`, `analyzer`, and `representer` run in docker on `python:
- - [ ] `.docs/instructions.md`(**required**) - - [ ] `.docs/introduction.md`(_optional_) - - [ ] `.docs/introduction.append.md`(_optional_) - - [ ] `.docs/instructions.append.md` (_optional_) - - [ ] `.docs/hints.md`(_optional_) - - [ ] `.meta/config.json` (**required**) - - [ ] `.meta/example.py` (**required**) - - [ ] `.meta/design.md` (_optional_) - - [ ] `.meta/template.j2` (_template for generating tests from canonical data_) - - [ ] `.meta/tests.toml` (_tests configuration from canonical data_) - - [ ] `_test.py` (_**auto-generated from canonical data**_) - - [ ] `.py` (**required**) + - [ ] `.docs/instructions.md`(**required**) + - [ ] `.docs/introduction.md`(_optional_) + - [ ] `.docs/introduction.append.md`(_optional_) + - [ ] `.docs/instructions.append.md` (_optional_) + - [ ] `.docs/hints.md`(_optional_) + - [ ] `.meta/config.json` (**required**) + - [ ] `.meta/example.py` (**required**) + - [ ] `.meta/design.md` (_optional_) + - [ ] `.meta/template.j2` (_template for generating tests from cannonical data_) + - [ ] `.meta/tests.toml` (_tests configuration from cannonical data_) + - [ ] `_test.py` (_**auto-generated from cannonical data**_) + - [ ] `.py` (**required**) - -
- -

External Libraries and Dependencies


-Our tooling (_runners, representers, and analyzers_) runs in isolated containers within the exercism website. Because of this isolation, exercises cannot rely on third-party or external libraries. Any library needed for an exercise or exercise tests must be incorporated as part of a tooling build, and noted for students who are using the CLI to solve problems locally. +Our tooling (_runners, analyzers and representers_) runs in isolated containers within the exercism website. Because of this, **exercises cannot rely on third-party or external libraries.** Any library needed for an exercise or exercise tests must be incorporated as part of the tooling build, and noted for students who are using the CLI to solve problems locally. -If your exercise depends on a third-party library (_aka not part of standard Python_), please consult with maintainers about it. We may or may not be able to accommodate the package. +If your exercise depends on a third-party library (_aka not part of standard Python_), please consult with maintainers about it. We may or may not be able to accommodate the package.
- +

Auto-Generated Test Files and Test Templates


-[**Practice exercises**][practice-exercise-files] inherit their definitions from the [problem-specifications][problem-specifications] repository in the form of _description files_. Exercise introductions, instructions and (_in the case of **many**, but not **all**_) test files are then machine-generated for each language track. +[**Practice exercises**][practice-exercise-files] inherit their definitions from the [problem-specifications][problem-specifications] repository in the form of _description files_. Exercise introductions, instructions and (_in the case of **many**, but not **all**_) test files are then machine-generated for each language track. -Changes to practice exercise _specifications_ should be raised/PR'd in [problem-specifications][problem-specifications] and approved by **3 track maintainers**. After an exercise change has gone through that process , related documents and tests for the Python track will need to be [re-generated](#generating-practice-exercise-documents) via [configlet][configlet]. Configlet is also used as part of the track CI, essential track and exercise linting, and other verification tasks. +Changes to practice exercise _specifications_ should be raised/PR'd in [problem-specifications][problem-specifications] and approved by **3 track maintainers**. After an exercise change has gone through that process, related documents and tests for the Python track will need to be [re-generated](#generating-practice-exercise-documents) via [configlet][configlet]. Configlet is also used as part of the track CI, essential track and exercise linting, and other verification tasks. -If a practice exercise has an auto-generated `_test.py` file, there will be a `.meta/template.j2` and a `.meta/tests.toml` file in the exercise directory. If an exercise implements Python track-specific tests, there may be a `.meta/additional_tests.json` to define them. These `additional_tests.json` files will automatically be included in test generation. +If a practice exercise has an auto-generated `_test.py` file, there will be a `.meta/template.j2` and a `.meta/tests.toml` file in the exercise directory. If an exercise implements Python track-specific tests, there may be a `.meta/additional_tests.json` to define them. These `additional_tests.json` files will automatically be included in test generation. _Exercise Structure with Auto-Generated Test Files_ -```Graphql +```Bash [/ ├── .docs │ └── instructions.md @@ -341,9 +314,10 @@ _Exercise Structure with Auto-Generated Test Files_ ├── .py #stub file └── -Practice exercise `_test.py` files are generated/regenerated via the [Python Track Test Generator][python-track-test-generator]. +Practice exercise `_test.py` files are generated/regenerated via the [Python Track Test Generator][python-track-test-generator]. Please reach out to a maintainer if you need any help with the process.
@@ -361,6 +335,7 @@ If an unimplemented exercise does not have a `canonical-data.json` file, the tes
**Example solution files serve two purposes only:** + 1. Verification of the tests 2. Example implementation for mentor/student reference @@ -376,7 +351,7 @@ Implementing Track-specific Practice Exercises is similar to implementing a `can
-

Generating Practice Exercise Documents

+

Generating Practice Exercise Documents


You will need @@ -388,6 +363,7 @@ Implementing Track-specific Practice Exercises is similar to implementing a `can ```bash configlet generate --spec-path path/to/problem/specifications --only example-exercise ``` +

For all Practice Exercises

```bash @@ -396,12 +372,8 @@ configlet generate --spec-path path/to/problem/specifications
- - [.flake8]: https://github.com/exercism/python/blob/main/.flake8 [.style.yapf]: https://github.com/exercism/python/blob/main/.style.yapf -[EOL]: https://en.wikipedia.org/wiki/Newline -[PEP8]: https://www.python.org/dev/peps/pep-0008/ [american-english]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md [being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member [card-games-testfile]: https://github.com/exercism/python/blob/main/exercises/concept/card-games/lists_test.py @@ -409,10 +381,12 @@ configlet generate --spec-path path/to/problem/specifications [concept-exercise-anatomy]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md [concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md [config-json]: https://github.com/exercism/javascript/blob/main/config.json +[configlet-general]: https://github.com/exercism/configlet [configlet-lint]: https://github.com/exercism/configlet#configlet-lint [configlet]: https://github.com/exercism/docs/blob/main/building/configlet/generating-documents.md [distinguishing-test-iterations]: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests [enumerate]: https://docs.python.org/3/library/functions.html#enumerate +[eol]: https://en.wikipedia.org/wiki/Newline [exercise-config-json]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md#full-example [exercise-presentation]: https://github.com/exercism/docs/blob/main/building/tracks/presentation.md [exercism-admins]: https://github.com/exercism/docs/blob/main/community/administrators.md @@ -437,12 +411,12 @@ configlet generate --spec-path path/to/problem/specifications [markdown-language]: https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf [open-an-issue]: https://github.com/exercism/python/issues/new/choose [pep8-for-humans]: https://pep8.org/ +[pep8]: https://www.python.org/dev/peps/pep-0008/ [practice-exercise-anatomy]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md [practice-exercise-files]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md#exercise-files [practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md [prettier]: https://prettier.io/ [problem-specifications]: https://github.com/exercism/problem-specifications -[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md [pylint-disable-check]: https://pylint.pycqa.org/en/latest/user_guide/message-control.html#block-disables [pylint]: https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html [pylintrc]: https://github.com/exercism/python/blob/main/pylintrc diff --git a/README.md b/README.md index 879557ac61..6493081636 100644 --- a/README.md +++ b/README.md @@ -3,66 +3,68 @@

Exercism Python Track

-
- -![Exercism_II](https://img.shields.io/badge/Exercism--Built-9101FF?logo=python&logoColor=FFDF58&labelColor=3D7AAB&label=Python-Powered) -[![Build Status](https://github.com/exercism/python/workflows/Exercises%20check/badge.svg)](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22) +                          [![Discourse topics](https://img.shields.io/discourse/topics?color=8A08E6&label=Connect%20&labelColor=FFDF58&logo=Discourse&logoColor=8A08E6&server=https%3A%2F%2Fforum.exercism.org&style=social)](https://forum.exercism.org) +  [![Exercism_II](https://img.shields.io/badge/Exercism--Built-9101FF?logo=python&logoColor=FFDF58&labelColor=3D7AAB&label=Python%203.10%20Powered)](https://exercism.org) +  [![Exercism_III](https://img.shields.io/badge/PAUSED-C73D4E?labelColor=3D454D&label=Contributions)](https://exercism.org/blog/freeing-our-maintainers) +  [![Build Status](https://github.com/exercism/python/workflows/Exercises%20check/badge.svg)](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22)
Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 +
+ **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. -This repo holds all the instructions, tests, code, & support files for Python *exercises* currently under development or implemented & available for students. +This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. - 🌟   Track exercises support Python `3.8`. - 🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.9`. +🌟   Track exercises support Python `3.7` - `3.10.6`. +Exceptions to this support are noted where they occur. + 🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.10.6`. -Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴  . Concept exercises are constrained to a small set of language or syntax features. Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory. +Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴  . Concept exercises are constrained to a small set of language or syntax features. Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory. -
+

+ +
+ + + + - +🌟🌟  Please take a moment to read our [Code of Conduct][exercism-code-of-conduct] 🌟🌟  +It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use]. -🌟🌟  Please take a moment to read our [Code of Conduct][exercism-code-of-conduct]. 🌟🌟  -It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member], [The words that we use][the-words-that-we-use], and [Pull Requests][prs]. +                         Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins] -Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins] +

- + -We 💛 💙   Pull Requests. **But our maintainers generally can't accept _unsolicited_ PRs.** -Check our [help wanted][open-issues] list or [open an issue ][open-an-issue] for discussion first. -We  ✨💙  💛  💙 ✨  [PRs][prs] that follow our **[Contributing Guidelines][contributing-guidelines]**. +We 💛 💙   our community. +**`But our maintainers are not accepting community contributions at this time.`** +Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details. +
+ + +Here to suggest a new feature or new exercise?? **Hooray!**  🎉   +We'd love if you did that via our [Exercism Community Forum](https://forum.exercism.org/). Please keep in mind [Chesterton's Fence][chestertons-fence]. +_Thoughtful suggestions will likely result faster & more enthusiastic responses from volunteers._
+ ✨ 🦄  _**Want to jump directly into Exercism specifications & detail?**_      [Structure][exercism-track-structure] **|** [Tasks][exercism-tasks] **|** [Concepts][exercism-concepts] **|** [Concept Exercises][concept-exercises] **|** [Practice Exercises][practice-exercises] **|** [Presentation][exercise-presentation]      [Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_✨ version in [contributing][website-contributing-section] on exercism.org_) -
- - -If you are here to help out with [open issues][open-issues], you have our gratitude  🙌  🙌🏽. -Anything with [`help wanted`] and without a [`Claimed`] tag is up for grabs. -Comment on the issue and we will reserve it for you.  🌈   ✨ - -
- - -Here to suggest a new feature or new exercise?? **Hooray!**  🎉   -Please keep in mind [Chesterton's Fence][chestertons-fence]. -_Thoughtful suggestions will likely result faster & more enthusiastic responses from maintainers._ - -
+

## Python Software and Documentation -**Copyright © 2001-2021 Python Software Foundation. All rights reserved.** +**Copyright © 2001-2022 Python Software Foundation. All rights reserved.** Python software and documentation are licensed under the [PSF License Agreement][psf-license]. @@ -70,17 +72,16 @@ Starting with `Python 3.8.6`, examples, recipes, and other code in the Python do Some software incorporated into Python is under different licenses. The licenses are listed with code falling under that license. See [Licenses and Acknowledgements for Incorporated Software](https://docs.python.org/3/license.html#otherlicenses) for an incomplete list of these licenses. +
## Exercism Python Track License -This repository uses the [MIT License](/LICENSE). - +This repository uses the [MIT License](/LICENSE). [being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member [chestertons-fence]: https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md [concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md [config-json]: https://github.com/exercism/python/blob/main/config.json -[contributing-guidelines]: https://github.com/exercism/python/blob/main/CONTRIBUTING.md [exercise-presentation]: https://github.com/exercism/docs/blob/main/building/tracks/presentation.md [exercism-admins]: https://github.com/exercism/docs/blob/main/community/administrators.md [exercism-code-of-conduct]: https://exercism.org/docs/using/legal/code-of-conduct @@ -93,10 +94,8 @@ This repository uses the [MIT License](/LICENSE). [exercism-track-structure]: https://github.com/exercism/docs/tree/main/building/tracks [exercism-website]: https://exercism.org/ [exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md -[open-an-issue]: https://github.com/exercism/python/issues/new/choose -[open-issues]: https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 -[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md [practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md +[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md [psf-license]: https://docs.python.org/3/license.html#psf-license [python-syllabus]: https://exercism.org/tracks/python/concepts [the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md From 6ff41f24ad097be9f3dfd4e7277201f02dd5585b Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 30 Nov 2022 12:48:08 +0100 Subject: [PATCH 097/826] Update autoresponder for pausing community contributions We realized belatedly that we should create a shared, re-usable workflow. This simplifies the workflow logic using a variable for the forum category that the new topic gets created in, if they click the link to copy their post from here to the forum. --- .../pause-community-contributions.yml | 55 ++++--------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/.github/workflows/pause-community-contributions.yml b/.github/workflows/pause-community-contributions.yml index 16b1ca555d..e91c86cf68 100644 --- a/.github/workflows/pause-community-contributions.yml +++ b/.github/workflows/pause-community-contributions.yml @@ -8,52 +8,17 @@ on: types: - opened paths-ignore: - - "exercises/*/*/.approaches/**" - - "exercises/*/*/.articles/**" + - 'exercises/*/*/.approaches/**' + - 'exercises/*/*/.articles/**' workflow_dispatch: +permissions: + issues: write + pull-requests: write + jobs: pause: - name: Pause Community Contributions - runs-on: ubuntu-22.04 - steps: - - name: Detect if user is org member - uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 - id: is-organization-member - with: - script: | - if (context.actor == 'dependabot' || context.actor == 'exercism-bot' || context.actor == 'github-actions[bot]') { - return true; - } - - return github.rest.orgs.checkMembershipForUser({ - org: context.repo.owner, - username: context.actor, - }).then(response => response.status == 204) - .catch(err => false); - - name: Comment - if: steps.is-organization-member.outputs.result == 'false' - uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 - with: - script: | - const isIssue = !!context.payload.issue; - const subject = context.payload.issue || context.payload.pull_request; - const thing = (isIssue ? 'issue' : 'PR'); - const aThing = (isIssue ? 'an issue' : 'a PR'); - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `Hello. Thanks for opening ${aThing} on Exercism. We are currently in a phase of our journey where we have paused community contributions to allow us to take a breather and redesign our community model. You can learn more in [this blog post](https://exercism.org/blog/freeing-our-maintainers). **As such, all issues and PRs in this repository are being automatically closed.**\n\nThat doesn’t mean we’re not interested in your ideas, or that if you’re stuck on something we don’t want to help. The best place to discuss things is with our community on the Exercism Community Forum. You can use [this link](https://forum.exercism.org/new-topic?title=${encodeURI(subject.title)}&body=${encodeURI(subject.body)}&category=python) to copy this into a new topic there.\n\n---\n\n_Note: If this ${thing} has been pre-approved, please link back to this ${thing} on the forum thread and a maintainer or staff member will reopen it._\n` - }) - - name: Close - if: steps.is-organization-member.outputs.result == 'false' - uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 - with: - script: | - github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - state: "closed", - }) + if: github.repository_owner == 'exercism' # Stops this job from running on forks + uses: exercism/github-actions/.github/workflows/community-contributions.yml@main + with: + forum_category: python From b46d0dfef65ccfd6988fc05115aa6f351d32c3b9 Mon Sep 17 00:00:00 2001 From: Meatball Date: Thu, 1 Dec 2022 23:33:08 +0100 Subject: [PATCH 098/826] Marked new_tests as excluded --- .../armstrong-numbers/.meta/tests.toml | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/exercises/practice/armstrong-numbers/.meta/tests.toml b/exercises/practice/armstrong-numbers/.meta/tests.toml index fdada6d1ef..b956bdfd47 100644 --- a/exercises/practice/armstrong-numbers/.meta/tests.toml +++ b/exercises/practice/armstrong-numbers/.meta/tests.toml @@ -1,30 +1,45 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [c1ed103c-258d-45b2-be73-d8c6d9580c7b] description = "Zero is an Armstrong number" [579e8f03-9659-4b85-a1a2-d64350f6b17a] -description = "Single digit numbers are Armstrong numbers" +description = "Single-digit numbers are Armstrong numbers" [2d6db9dc-5bf8-4976-a90b-b2c2b9feba60] -description = "There are no 2 digit Armstrong numbers" +description = "There are no two-digit Armstrong numbers" [509c087f-e327-4113-a7d2-26a4e9d18283] -description = "Three digit number that is an Armstrong number" +description = "Three-digit number that is an Armstrong number" [7154547d-c2ce-468d-b214-4cb953b870cf] -description = "Three digit number that is not an Armstrong number" +description = "Three-digit number that is not an Armstrong number" [6bac5b7b-42e9-4ecb-a8b0-4832229aa103] -description = "Four digit number that is an Armstrong number" +description = "Four-digit number that is an Armstrong number" [eed4b331-af80-45b5-a80b-19c9ea444b2e] -description = "Four digit number that is not an Armstrong number" +description = "Four-digit number that is not an Armstrong number" [f971ced7-8d68-4758-aea1-d4194900b864] -description = "Seven digit number that is an Armstrong number" +description = "Seven-digit number that is an Armstrong number" [7ee45d52-5d35-4fbd-b6f1-5c8cd8a67f18] -description = "Seven digit number that is not an Armstrong number" +description = "Seven-digit number that is not an Armstrong number" + +[5ee2fdf8-334e-4a46-bb8d-e5c19c02c148] +description = "Armstrong number containing seven zeroes" +include = false + +[12ffbf10-307a-434e-b4ad-c925680e1dd4] +description = "The largest and last Armstrong number" +include = false From 805c1c07359f317daec973d857eb67add9096f1a Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 18 Nov 2022 05:54:06 -0600 Subject: [PATCH 099/826] Create config.json --- .../practice/isogram/.approaches/config.json | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/config.json diff --git a/exercises/practice/isogram/.approaches/config.json b/exercises/practice/isogram/.approaches/config.json new file mode 100644 index 0000000000..daf98835d0 --- /dev/null +++ b/exercises/practice/isogram/.approaches/config.json @@ -0,0 +1,42 @@ +{ + "introduction": { + "authors": ["bobahop"] + }, + "approaches": [ + { + "uuid": "5f06953f-6002-437d-a9a4-6aa5c7ebf247", + "slug": "scrub-comprehension", + "title": "Scrub with a list comprehension", + "blurb": "Use a list comprehension and set to return the answer.", + "authors": ["bobahop"] + }, + { + "uuid": "cc399b36-e9d5-4a97-857d-6ef9dea50115", + "slug": "scrub-replace", + "title": "Scrub with a series of replace calls", + "blurb": "Use a series of replace calls and set to return the answer.", + "authors": ["bobahop"] + }, + { + "uuid": "0f0c738e-885a-4431-a592-ee12d25ae9f4", + "slug": "scrub-regex", + "title": "Scrub with a regex", + "blurb": "Use a re.sub and set to return the answer.", + "authors": ["bobahop"] + }, + { + "uuid": "9d733e59-dbc4-45f7-9c60-1b9c99020687", + "slug": "findall-regex", + "title": "Use re.findall to get valid letters", + "blurb": "Use a re.findall and set to return the answer.", + "authors": ["bobahop"] + }, + { + "uuid": "b87e1bcf-0c40-416e-b6cc-684a57c9455c", + "slug": "bitfield", + "title": "Bit field using a for loop", + "blurb": "Use a bit field with a for loop to keep track of used letters.", + "authors": ["bobahop"] + } + ] +} From 6e75340094aa883e232fbc682c84e9dfab19ab63 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 18 Nov 2022 05:57:14 -0600 Subject: [PATCH 100/826] Create config.json --- exercises/practice/isogram/.articles/config.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 exercises/practice/isogram/.articles/config.json diff --git a/exercises/practice/isogram/.articles/config.json b/exercises/practice/isogram/.articles/config.json new file mode 100644 index 0000000000..7ffb9bab18 --- /dev/null +++ b/exercises/practice/isogram/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "c933d353-8857-48f9-bac3-1ac752676390", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach to determining an isogram.", + "authors": ["bobahop"] + } + ] +} From c63b15ea7f94086c607f30db1d557bb22604cd8e Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 18 Nov 2022 05:58:13 -0600 Subject: [PATCH 101/826] Create snippet.md --- .../practice/isogram/.articles/performance/snippet.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 exercises/practice/isogram/.articles/performance/snippet.md diff --git a/exercises/practice/isogram/.articles/performance/snippet.md b/exercises/practice/isogram/.articles/performance/snippet.md new file mode 100644 index 0000000000..3a0439bfc4 --- /dev/null +++ b/exercises/practice/isogram/.articles/performance/snippet.md @@ -0,0 +1,7 @@ +``` +scrubbed comprehension: 3.118929599993862e-06 +scrubbed replace: 9.586393000063253e-07 +scrubbed regex: 1.8171838999987813e-06 +findall regex: 4.059006099996623e-06 +bitfield: 5.4183307999919636e-06 +``` From 7b72b881e53fd78ec1ec65fb2d95d05292cd6a0b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 18 Nov 2022 05:59:19 -0600 Subject: [PATCH 102/826] Create Benchmark.py --- .../.articles/performance/code/Benchmark.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 exercises/practice/isogram/.articles/performance/code/Benchmark.py diff --git a/exercises/practice/isogram/.articles/performance/code/Benchmark.py b/exercises/practice/isogram/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..47790fdc4a --- /dev/null +++ b/exercises/practice/isogram/.articles/performance/code/Benchmark.py @@ -0,0 +1,76 @@ +import timeit + +loops = 1_000_000 + +val = timeit.timeit("""is_isogram("Emily Jung-Schwartzkopf")""", + """ +def is_isogram(phrase): + scrubbed = [ltr.lower() for ltr in phrase if ltr.isalpha()] + return len(set(scrubbed)) == len(scrubbed) + +""", number=loops) / loops + +print(f"scrubbed comprehension: {val}") + +val = timeit.timeit("""is_isogram("Emily Jung-Schwartzkopf")""", + """ +def is_isogram(phrase): + scrubbed = phrase.replace('-', '').replace(' ', '').lower() + return len(scrubbed) == len(set(scrubbed)) + +""", number=loops) / loops + +print(f"scrubbed replace: {val}") + +val = timeit.timeit("""is_isogram("Emily Jung-Schwartzkopf")""", + """ +import re + +def is_isogram(phrase): + scrubbed = re.compile('[^a-zA-Z]').sub('', phrase).lower() + return len(set(scrubbed)) == len(scrubbed) + +""", number=loops) / loops + +print(f"scrubbed regex: {val}") + +val = timeit.timeit("""is_isogram("Emily Jung-Schwartzkopf")""", + """ +import re + +def is_isogram(phrase): + scrubbed = "".join(re.findall("[a-zA-Z]", phrase)).lower() + return len(set(scrubbed)) == len(scrubbed) + +""", number=loops) / loops + +print(f"findall regex: {val}") + +val = timeit.timeit("""is_isogram("Emily Jung-Schwartzkopf")""", + """ +A_LCASE = 97 +Z_LCASE = 122 +A_UCASE = 65 +Z_UCASE = 90 + + +def is_isogram(phrase): + letter_flags = 0 + + for ltr in phrase: + letter = ord(ltr) + if letter >= A_LCASE and letter <= Z_LCASE: + if letter_flags & (1 << (letter - A_LCASE)) != 0: + return False + else: + letter_flags |= 1 << (letter - A_LCASE) + elif letter >= A_UCASE and letter <= Z_UCASE: + if letter_flags & (1 << (letter - A_UCASE)) != 0: + return False + else: + letter_flags |= 1 << (letter - A_UCASE) + return True + +""", number=loops) / loops + +print(f"bitfield: {val}") From dc9e74bfad2261af0c625c764945cac7d8c429c9 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 18 Nov 2022 06:24:46 -0600 Subject: [PATCH 103/826] Create content.md --- .../isogram/.articles/performance/content.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 exercises/practice/isogram/.articles/performance/content.md diff --git a/exercises/practice/isogram/.articles/performance/content.md b/exercises/practice/isogram/.articles/performance/content.md new file mode 100644 index 0000000000..8ee1f1b9af --- /dev/null +++ b/exercises/practice/isogram/.articles/performance/content.md @@ -0,0 +1,42 @@ +# Performance + +In this approach, we'll find out how to most efficiently determine if a string is an Isogram in Python. + +The [approaches page][approaches] lists four idiomatic approaches to this exercise: + +1. [Using a list comprehension and `set`][approach-scrub-comprehension] +2. [Using `replace` and `set`][approach-scrub-replace] +3. [Using a `re.sub` and `set`][approach-scrub-regex] +4. [Using a `re.findall` and `set`][approach-findall-regex] + +For our performance investigation, we'll also include a fifth approach that [uses a bit field to keep track of used letters][approach-bitfield]. + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. + +``` +scrubbed comprehension: 3.118929599993862e-06 +scrubbed replace: 9.586393000063253e-07 +scrubbed regex: 1.8171838999987813e-06 +findall regex: 4.059006099996623e-06 +bitfield: 5.4183307999919636e-06 +``` + +The four fastest approaches use `set`. + +- Calling a series of `replace` methods to scrub the input was the fastest approach at about 959 nanoseconds. +- Next fastest was using `re.sub` to scrub the input, at about 1817 nanoseconds. +- Third fastest was using a list comprehension to scrub the input, at about 3119 nanoseconds. +- Using `re.findall` to scrub the input was fourth fastest, at about 4059 nanoseconds. +- Although the bit field approach may be faster in other languages, it is significantly slower in Python. +It was slower than all of the `set` approaches, at about 5418 nanoseconds. + +[approaches]: https://exercism.org/tracks/python/exercises/isogram/approaches +[approach-scrub-comprehension]: https://exercism.org/tracks/python/exercises/isogram/approaches/scrub-comprehension +[approach-scrub-replace]: https://exercism.org/tracks/python/exercises/isogram/approaches/scrub-replace +[approach-scrub-regex]: https://exercism.org/tracks/python/exercises/isogram/approaches/scrub-regex +[approach-findall-regex]: https://exercism.org/tracks/python/exercises/isogram/approaches/findall-regex +[approach-bitfield]: https://exercism.org/tracks/python/exercises/isogram/approaches/bitfield +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/isogram/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html From e35fd58f10463a2eb5a2ed039756467a0bf1e40e Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 18 Nov 2022 08:35:28 -0600 Subject: [PATCH 104/826] Create introduction.md --- .../isogram/.approaches/introduction.md | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/introduction.md diff --git a/exercises/practice/isogram/.approaches/introduction.md b/exercises/practice/isogram/.approaches/introduction.md new file mode 100644 index 0000000000..6616e5a194 --- /dev/null +++ b/exercises/practice/isogram/.approaches/introduction.md @@ -0,0 +1,90 @@ +# Introduction + +There are many idiomatic ways to solve Isogram. +Among them are: +- You can scrub the input with a list comprehension and then compare the `len()` of the scrubbed letters with the `len()` of a `set` of the scrubbed letters. +- You can scrub the input with a couple of calls to `replace()` and then compare the `len()` of the scrubbed letters with the `len()` of a `set` of the scrubbed letters. +- You can scrub the input with a `re.sub()` and then compare the `len()` of the scrubbed letters with the `len()` of a `set` of the scrubbed letters. +- You can filter the input with a `re.findall()` and then compare the `len()` of the scrubbed letters with the `len()` of a `set` of the scrubbed letters. + +## General guidance + +The key to solving Isogram is to determine if any of the letters in the input are repeated. +A repeated letter means the input is not an isogram. +The letters are "scrubbed" or filtered so that non-alphabetic characters are not considered when determining an isogram. +The occurrence of the letter `a` and the letter `A` count as a repeated letter, so `Alpha` would not be an isogram. + +The following four approaches compare the length of the scrubbed letters with the length of a `set`of the scrubbed letters. + +## Approach: scrub with a list comprehension + +```python +def is_isogram(phrase): + scrubbed = [ltr.lower() for ltr in phrase if ltr.isalpha()] + return len(set(scrubbed)) == len(scrubbed) + +``` + +For more information, check the [scrub with list comprehension approach][approach-scrub-comprehension] + +## Approach: scrub with `replace()` + +```python +def is_isogram(phrase): + scrubbed = phrase.replace('-', '').replace(' ', '').lower() + return len(scrubbed) == len(set(scrubbed)) + +``` + +For more information, check the [scrub with `replace()` approach][approach-scrub-replace] + +## Approach: scrub with `re.sub()` + +```python +import re + + +def is_isogram(phrase): + scrubbed = re.compile('[^a-zA-Z]').sub('', phrase).lower() + return len(set(scrubbed)) == len(scrubbed) + +``` + +For more information, check the [scrub with `re.sub()` approach][approach-scrub-regex] + +## Approach: filter with `re.findall()` + +```python +import re + + +def is_isogram(phrase): + scrubbed = "".join(re.findall("[a-zA-Z]", phrase)).lower() + return len(set(scrubbed)) == len(scrubbed) + +``` + +For more information, check the [filter with `re.findall()` approach][approach-scrub-regex] + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +### Other approach: Bit field + +Another approach can use a bit field to keep track of used letters. +For more information, check the [bit field approach][approach-bitfield]. + +## Which approach to use? + +All four `set` approaches are idiomatic. +The `replace` approach is the fastest. + +To compare performance of the approaches, check the [Performance article][article-performance]. + +[approach-scrub-comprehension]: https://exercism.org/tracks/python/exercises/isogram/approaches/scrub-comprehension +[approach-scrub-replace]: https://exercism.org/tracks/python/exercises/isogram/approaches/scrub-replace +[approach-scrub-regex]: https://exercism.org/tracks/python/exercises/isogram/approaches/scrub-regex +[approach-findall-regex]: https://exercism.org/tracks/python/exercises/isogram/approaches/findall-regex +[approach-bitfield]: https://exercism.org/tracks/python/exercises/isogram/approaches/bitfield +[article-performance]: https://exercism.org/tracks/python/exercises/isogram/articles/performance From ba21ad766b83c313c944bcd00d2b8b00e857d2f2 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 13:27:52 -0600 Subject: [PATCH 105/826] Create snippet.txt --- .../isogram/.approaches/scrub-comprehension/snippet.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/scrub-comprehension/snippet.txt diff --git a/exercises/practice/isogram/.approaches/scrub-comprehension/snippet.txt b/exercises/practice/isogram/.approaches/scrub-comprehension/snippet.txt new file mode 100644 index 0000000000..9bf47270fa --- /dev/null +++ b/exercises/practice/isogram/.approaches/scrub-comprehension/snippet.txt @@ -0,0 +1,3 @@ +def is_isogram(phrase): + scrubbed = [ltr.lower() for ltr in phrase if ltr.isalpha()] + return len(set(scrubbed)) == len(scrubbed) From bae0024fca06745e6bb763e0ee93b51155092c3b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:09:54 -0600 Subject: [PATCH 106/826] Create content.md --- .../scrub-comprehension/content.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/scrub-comprehension/content.md diff --git a/exercises/practice/isogram/.approaches/scrub-comprehension/content.md b/exercises/practice/isogram/.approaches/scrub-comprehension/content.md new file mode 100644 index 0000000000..04dc5bf0e2 --- /dev/null +++ b/exercises/practice/isogram/.approaches/scrub-comprehension/content.md @@ -0,0 +1,36 @@ +# Scrub with a list comprehension + +```python +def is_isogram(phrase): + scrubbed = [ltr.lower() for ltr in phrase if ltr.isalpha()] + return len(set(scrubbed)) == len(scrubbed) + +``` + +For this approach, a [list comprehension][list-comprehension] is used to iterate the letters in the input phrase [str][str]ing. + +- In the code example, `ltr` is the name given to each letter iterated in the [`for`][for] loop. +- The result of each iteration is `ltr.lower()`, which is the [`lower`][lower]cased letter being iterated. +All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, +since `A` and `a` are considered to be the same letter. +- The iterable part of the list comprehension is the input phrase. +- The letters are filtered by the use of the [optional conditional logic][conditional-logic]: `if ltr.isalpha()`. +[`isalpha()`][isalpha] returns `True` if the letter being iterated is alphabetic. + +When the list comprehension is done, the `scrubbed` variable will be a list holding only lowercased alphabetic characters. +- A [`set`][set] is constructed from the `scrubbed` list and its [`len`][len] is compared with the `len` of the the `scrubbed` list. +Since a `set` holds only unique values, the phrase will be an isogram if its number of unique letters is the same as its total number of letters. +The function returns whether the number of unique letters equals the total number of letters. +- For `Alpha` it would return `False`, because `a` is considered to repeat `A`, so the number of unique letters in `Alpha` is `4`, +and the total number of letters in `Alpha` is `5`. +- For `Bravo` it would return `True`, since the number of unique letters in `Bravo` is `5`, and the total number of letters in `Bravo` is `5`. + + +[list-comprehension]: https://realpython.com/list-comprehension-python/#using-list-comprehensions +[str]: https://docs.python.org/3/library/stdtypes.html#textseq +[lower]: https://docs.python.org/3/library/stdtypes.html?highlight=lower#str.lower +[for]: https://realpython.com/python-for-loop/#the-python-for-loop +[conditional-logic]: https://realpython.com/list-comprehension-python/#using-conditional-logic +[isalpha]: https://docs.python.org/3/library/stdtypes.html?highlight=lower#str.isalpha +[set]: https://docs.python.org/3/library/stdtypes.html?highlight=set#set +[len]: https://docs.python.org/3/library/functions.html?highlight=len#len From b9eef38434ea166e1dc5a22229f625db86bf055e Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:11:46 -0600 Subject: [PATCH 107/826] Create snippet.txt --- .../practice/isogram/.approaches/scrub-replace/snippet.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/scrub-replace/snippet.txt diff --git a/exercises/practice/isogram/.approaches/scrub-replace/snippet.txt b/exercises/practice/isogram/.approaches/scrub-replace/snippet.txt new file mode 100644 index 0000000000..586fb98442 --- /dev/null +++ b/exercises/practice/isogram/.approaches/scrub-replace/snippet.txt @@ -0,0 +1,3 @@ +def is_isogram(phrase): + scrubbed = phrase.replace('-', '').replace(' ', '').lower() + return len(scrubbed) == len(set(scrubbed)) From 28880732a4a83ecb49737264bff9ec922621acec Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:12:27 -0600 Subject: [PATCH 108/826] Create snippet.txt --- .../practice/isogram/.approaches/scrub-regex/snippet.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/scrub-regex/snippet.txt diff --git a/exercises/practice/isogram/.approaches/scrub-regex/snippet.txt b/exercises/practice/isogram/.approaches/scrub-regex/snippet.txt new file mode 100644 index 0000000000..e95ebddd6e --- /dev/null +++ b/exercises/practice/isogram/.approaches/scrub-regex/snippet.txt @@ -0,0 +1,6 @@ +import re + + +def is_isogram(phrase): + scrubbed = re.compile('[^a-zA-Z]').sub('', phrase).lower() + return len(set(scrubbed)) == len(scrubbed) From 757a8b492950ccf195338408ee51ff2d51fbd4a0 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:13:12 -0600 Subject: [PATCH 109/826] Create snippet.txt --- .../practice/isogram/.approaches/findall-regex/snippet.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/findall-regex/snippet.txt diff --git a/exercises/practice/isogram/.approaches/findall-regex/snippet.txt b/exercises/practice/isogram/.approaches/findall-regex/snippet.txt new file mode 100644 index 0000000000..313670c840 --- /dev/null +++ b/exercises/practice/isogram/.approaches/findall-regex/snippet.txt @@ -0,0 +1,6 @@ +import re + + +def is_isogram(phrase): + scrubbed = "".join(re.findall("[a-zA-Z]", phrase)).lower() + return len(set(scrubbed)) == len(scrubbed) From 6c06d9ef563538dcde0ed620bdcb60c0cb711dd1 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:15:54 -0600 Subject: [PATCH 110/826] Create content.md --- .../isogram/.approaches/bitfield/content.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/bitfield/content.md diff --git a/exercises/practice/isogram/.approaches/bitfield/content.md b/exercises/practice/isogram/.approaches/bitfield/content.md new file mode 100644 index 0000000000..3cf367a19d --- /dev/null +++ b/exercises/practice/isogram/.approaches/bitfield/content.md @@ -0,0 +1,27 @@ +# Bitfield to keep track of used letters + +```python +A_LCASE = 97 +Z_LCASE = 122 +A_UCASE = 65 +Z_UCASE = 90 + + +def is_isogram(phrase): + letter_flags = 0 + + for ltr in phrase: + letter = ord(ltr) + if letter >= A_LCASE and letter <= Z_LCASE: + if letter_flags & (1 << (letter - A_LCASE)) != 0: + return False + else: + letter_flags |= 1 << (letter - A_LCASE) + elif letter >= A_UCASE and letter <= Z_UCASE: + if letter_flags & (1 << (letter - A_UCASE)) != 0: + return False + else: + letter_flags |= 1 << (letter - A_UCASE) + return True + +``` From f32e5ec911008158566ce2c3906dc55b52cafd92 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:17:09 -0600 Subject: [PATCH 111/826] Create snippet.txt --- .../practice/isogram/.approaches/bitfield/snippet.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/bitfield/snippet.txt diff --git a/exercises/practice/isogram/.approaches/bitfield/snippet.txt b/exercises/practice/isogram/.approaches/bitfield/snippet.txt new file mode 100644 index 0000000000..802615392a --- /dev/null +++ b/exercises/practice/isogram/.approaches/bitfield/snippet.txt @@ -0,0 +1,7 @@ +for ltr in phrase: + letter = ord(ltr) + if letter >= A_LCASE and letter <= Z_LCASE: + if letter_flags & (1 << (letter - A_LCASE)) != 0: + return False + else: + letter_flags |= 1 << (letter - A_LCASE) From 81c379b117b8eb0dc2946a9115b3d90d0f7c481b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:18:06 -0600 Subject: [PATCH 112/826] Create content.md --- .../practice/isogram/.approaches/scrub-replace/content.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/scrub-replace/content.md diff --git a/exercises/practice/isogram/.approaches/scrub-replace/content.md b/exercises/practice/isogram/.approaches/scrub-replace/content.md new file mode 100644 index 0000000000..72606784fd --- /dev/null +++ b/exercises/practice/isogram/.approaches/scrub-replace/content.md @@ -0,0 +1,8 @@ +# Scrub with `replace()` + +```python +def is_isogram(phrase): + scrubbed = phrase.replace('-', '').replace(' ', '').lower() + return len(scrubbed) == len(set(scrubbed)) + +``` From cd47b94258952fd0aaf33f9117651fa09914fb48 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:18:43 -0600 Subject: [PATCH 113/826] Create content.md --- .../isogram/.approaches/scrub-regex/content.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/scrub-regex/content.md diff --git a/exercises/practice/isogram/.approaches/scrub-regex/content.md b/exercises/practice/isogram/.approaches/scrub-regex/content.md new file mode 100644 index 0000000000..5da783f027 --- /dev/null +++ b/exercises/practice/isogram/.approaches/scrub-regex/content.md @@ -0,0 +1,11 @@ +# Scrub with `re.sub()` + +```python +import re + + +def is_isogram(phrase): + scrubbed = re.compile('[^a-zA-Z]').sub('', phrase).lower() + return len(set(scrubbed)) == len(scrubbed) + +``` From 9b66ad1004f76aadb0f2413aa0e06e34faf58fc3 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:19:18 -0600 Subject: [PATCH 114/826] Create content.md --- .../isogram/.approaches/findall-regex/content.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 exercises/practice/isogram/.approaches/findall-regex/content.md diff --git a/exercises/practice/isogram/.approaches/findall-regex/content.md b/exercises/practice/isogram/.approaches/findall-regex/content.md new file mode 100644 index 0000000000..4032df2c67 --- /dev/null +++ b/exercises/practice/isogram/.approaches/findall-regex/content.md @@ -0,0 +1,11 @@ +# Filter with `re.findall()` + +```python +import re + + +def is_isogram(phrase): + scrubbed = "".join(re.findall("[a-zA-Z]", phrase)).lower() + return len(set(scrubbed)) == len(scrubbed) + +``` From 7c3f339790ebfde7dca9f2dac06585c3f4654b31 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Sat, 26 Nov 2022 06:59:43 -0600 Subject: [PATCH 115/826] Update content.md --- .../.approaches/scrub-replace/content.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/exercises/practice/isogram/.approaches/scrub-replace/content.md b/exercises/practice/isogram/.approaches/scrub-replace/content.md index 72606784fd..91ea8ea3d8 100644 --- a/exercises/practice/isogram/.approaches/scrub-replace/content.md +++ b/exercises/practice/isogram/.approaches/scrub-replace/content.md @@ -6,3 +6,26 @@ def is_isogram(phrase): return len(scrubbed) == len(set(scrubbed)) ``` + +For this approach, [`replace()`][replace] is called a couple times to scrub the input phrase [str][str]ing. +Thw two `replace()` calls are [chained][method-chaining], so the result of the first `replace()` is the input for the next `replace()`. +The result of the last `replace()` is the input for `lower()`. +All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, +since `A` and `a` are considered to be the same letter. +When the replacing and lowercasing is done, the `scrubbed` variable will be a string having no hyphens or spaces, +and with all alphabetic letters lowercased. +- A [`set`][set] is constructed from the `scrubbed` string and its [`len`][len] is compared with the `len` of the the `scrubbed` string. +Since a `set` holds only unique values, the phrase will be an isogram if its number of unique letters is the same as its total number of letters. +The function returns whether the number of unique letters equals the total number of letters. +- For `Alpha` it would return `False`, because `a` is considered to repeat `A`, so the number of unique letters in `Alpha` is `4`, +and the total number of letters in `Alpha` is `5`. +- For `Bravo` it would return `True`, since the number of unique letters in `Bravo` is `5`, and the total number of letters in `Bravo` is `5`. + + +[replace]: https://docs.python.org/3/library/stdtypes.html?highlight=replace#str.replace +[str]: https://docs.python.org/3/library/stdtypes.html#textseq +[method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining +[lower]: https://docs.python.org/3/library/stdtypes.html?highlight=lower#str.lower +[set]: https://docs.python.org/3/library/stdtypes.html?highlight=set#set +[len]: https://docs.python.org/3/library/functions.html?highlight=len#len + From 108f0251b7fa62b3c7b7fc67574438e8fb54266b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Sat, 26 Nov 2022 07:23:07 -0600 Subject: [PATCH 116/826] Update content.md --- .../.approaches/scrub-regex/content.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/exercises/practice/isogram/.approaches/scrub-regex/content.md b/exercises/practice/isogram/.approaches/scrub-regex/content.md index 5da783f027..0676bd1214 100644 --- a/exercises/practice/isogram/.approaches/scrub-regex/content.md +++ b/exercises/practice/isogram/.approaches/scrub-regex/content.md @@ -9,3 +9,36 @@ def is_isogram(phrase): return len(set(scrubbed)) == len(scrubbed) ``` + +For this approach, [regular expression][regex], also known as a [regex][regex-how-to], is used to scrub the input phrase [str][str]ing. +- In the pattern of `[^a-zA-Z]` the brackets are used to define a character set that looks for characters which are not `a` through `z` and `A` through `Z`. +```exercism/note +If the first character of a character set is `^`, all the characters that are _not_ in the rest of the set will be matched. +``` +This essentially matches any characters which are not in the English alphabet. + The pattern is passed to the [`compile()`][compile] method to construct a [regular expression object][regex-object]. +- The [`sub()`][sub] method is then called on the regex object. +The `sub()` method replaces all non-alphabetic characters in the input phrase with an empty string. +- The result of `sub()` is then [chained][method-chaining] as the input for [`lower()`][lower]. +All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, +since `A` and `a` are considered to be the same letter. +When the replacing and lowercasing is done, the `scrubbed` variable will be a string having all alphabetic letters lowercased. +- A [`set`][set] is constructed from the `scrubbed` string and its [`len`][len] is compared with the `len` of the the `scrubbed` string. +Since a `set` holds only unique values, the phrase will be an isogram if its number of unique letters is the same as its total number of letters. +The function returns whether the number of unique letters equals the total number of letters. +- For `Alpha` it would return `False`, because `a` is considered to repeat `A`, so the number of unique letters in `Alpha` is `4`, +and the total number of letters in `Alpha` is `5`. +- For `Bravo` it would return `True`, since the number of unique letters in `Bravo` is `5`, and the total number of letters in `Bravo` is `5`. + + +[regex]: https://docs.python.org/3/library/re.html +[regex-how-to]: https://docs.python.org/3/howto/regex.html +[str]: https://docs.python.org/3/library/stdtypes.html#textseq +[compile]: https://docs.python.org/3/library/re.html?#re.compile +[regex-object]: https://docs.python.org/3/library/re.html?#re-objects +[sub]: https://docs.python.org/3/library/re.html?#re.sub +[method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining +[lower]: https://docs.python.org/3/library/stdtypes.html?highlight=lower#str.lower +[set]: https://docs.python.org/3/library/stdtypes.html?highlight=set#set +[len]: https://docs.python.org/3/library/functions.html?highlight=len#len + From baeaa3fef5122ea3333440ebc41c9c52b2a264c3 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Sat, 26 Nov 2022 07:23:46 -0600 Subject: [PATCH 117/826] Update content.md --- exercises/practice/isogram/.approaches/scrub-replace/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/isogram/.approaches/scrub-replace/content.md b/exercises/practice/isogram/.approaches/scrub-replace/content.md index 91ea8ea3d8..8c6989717c 100644 --- a/exercises/practice/isogram/.approaches/scrub-replace/content.md +++ b/exercises/practice/isogram/.approaches/scrub-replace/content.md @@ -9,7 +9,7 @@ def is_isogram(phrase): For this approach, [`replace()`][replace] is called a couple times to scrub the input phrase [str][str]ing. Thw two `replace()` calls are [chained][method-chaining], so the result of the first `replace()` is the input for the next `replace()`. -The result of the last `replace()` is the input for `lower()`. +The result of the last `replace()` is the input for [`lower()`][lower]. All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, since `A` and `a` are considered to be the same letter. When the replacing and lowercasing is done, the `scrubbed` variable will be a string having no hyphens or spaces, From ac6bd1a9ffa0fc2487b9142ff87df886e3a538de Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Sat, 26 Nov 2022 07:36:58 -0600 Subject: [PATCH 118/826] Update content.md --- .../.approaches/findall-regex/content.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/exercises/practice/isogram/.approaches/findall-regex/content.md b/exercises/practice/isogram/.approaches/findall-regex/content.md index 4032df2c67..bc4a6f619a 100644 --- a/exercises/practice/isogram/.approaches/findall-regex/content.md +++ b/exercises/practice/isogram/.approaches/findall-regex/content.md @@ -9,3 +9,32 @@ def is_isogram(phrase): return len(set(scrubbed)) == len(scrubbed) ``` + +For this approach, [regular expression pattern][regex], also known as a [regex][regex-how-to], is used to filter the input phrase [str][str]ing. +- In the pattern of `[a-zA-Z]` the brackets are used to define a character set that looks for characters which are `a` through `z` or `A` through `Z`. +This essentially matches any characters which are in the English alphabet. +The pattern is passed to the [`findall()`][findall] method to return a list of matched characters. +- The result of `findall()` is passed as an argument to the [`join()`][join] method, which is called on an empty string. +This makes a string out of the list of matched characters. +- The output of `join()` is then [chained][method-chaining] as the input for [`lower()`][lower]. +All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, +since `A` and `a` are considered to be the same letter. +When the filtering and lowercasing is done, the `scrubbed` variable will be a string having all alphabetic letters lowercased. +- A [`set`][set] is constructed from the `scrubbed` string and its [`len`][len] is compared with the `len` of the the `scrubbed` string. +Since a `set` holds only unique values, the phrase will be an isogram if its number of unique letters is the same as its total number of letters. +The function returns whether the number of unique letters equals the total number of letters. +- For `Alpha` it would return `False`, because `a` is considered to repeat `A`, so the number of unique letters in `Alpha` is `4`, +and the total number of letters in `Alpha` is `5`. +- For `Bravo` it would return `True`, since the number of unique letters in `Bravo` is `5`, and the total number of letters in `Bravo` is `5`. + + +[regex]: https://docs.python.org/3/library/re.html +[regex-how-to]: https://docs.python.org/3/howto/regex.html +[str]: https://docs.python.org/3/library/stdtypes.html#textseq +[findall]: https://docs.python.org/3/library/re.html?#re.findall +[join]: https://docs.python.org/3/library/stdtypes.html?#str.join +[method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining +[lower]: https://docs.python.org/3/library/stdtypes.html?highlight=lower#str.lower +[set]: https://docs.python.org/3/library/stdtypes.html?highlight=set#set +[len]: https://docs.python.org/3/library/functions.html?highlight=len#len + From 1c39403ab12e163912e719a95696b40fa20fc599 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Sat, 26 Nov 2022 07:37:31 -0600 Subject: [PATCH 119/826] Update content.md --- exercises/practice/isogram/.approaches/scrub-regex/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/isogram/.approaches/scrub-regex/content.md b/exercises/practice/isogram/.approaches/scrub-regex/content.md index 0676bd1214..0d2316c05b 100644 --- a/exercises/practice/isogram/.approaches/scrub-regex/content.md +++ b/exercises/practice/isogram/.approaches/scrub-regex/content.md @@ -19,7 +19,7 @@ This essentially matches any characters which are not in the English alphabet. The pattern is passed to the [`compile()`][compile] method to construct a [regular expression object][regex-object]. - The [`sub()`][sub] method is then called on the regex object. The `sub()` method replaces all non-alphabetic characters in the input phrase with an empty string. -- The result of `sub()` is then [chained][method-chaining] as the input for [`lower()`][lower]. +- The output of `sub()` is then [chained][method-chaining] as the input for [`lower()`][lower]. All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, since `A` and `a` are considered to be the same letter. When the replacing and lowercasing is done, the `scrubbed` variable will be a string having all alphabetic letters lowercased. From b70fa6f831e572c2ea4b72387b4ffac841bd49b2 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Sat, 26 Nov 2022 07:38:07 -0600 Subject: [PATCH 120/826] Update content.md --- .../practice/isogram/.approaches/scrub-replace/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/isogram/.approaches/scrub-replace/content.md b/exercises/practice/isogram/.approaches/scrub-replace/content.md index 8c6989717c..dd65e9ab8c 100644 --- a/exercises/practice/isogram/.approaches/scrub-replace/content.md +++ b/exercises/practice/isogram/.approaches/scrub-replace/content.md @@ -8,8 +8,8 @@ def is_isogram(phrase): ``` For this approach, [`replace()`][replace] is called a couple times to scrub the input phrase [str][str]ing. -Thw two `replace()` calls are [chained][method-chaining], so the result of the first `replace()` is the input for the next `replace()`. -The result of the last `replace()` is the input for [`lower()`][lower]. +Thw two `replace()` calls are [chained][method-chaining], so the output of the first `replace()` is the input for the next `replace()`. +The output of the last `replace()` is the input for [`lower()`][lower]. All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, since `A` and `a` are considered to be the same letter. When the replacing and lowercasing is done, the `scrubbed` variable will be a string having no hyphens or spaces, From 18e3195f02cf0edfeceff8933d6eacc8ea8b5263 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Sat, 26 Nov 2022 09:10:35 -0600 Subject: [PATCH 121/826] Update content.md --- .../isogram/.approaches/bitfield/content.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/exercises/practice/isogram/.approaches/bitfield/content.md b/exercises/practice/isogram/.approaches/bitfield/content.md index 3cf367a19d..d522330ec1 100644 --- a/exercises/practice/isogram/.approaches/bitfield/content.md +++ b/exercises/practice/isogram/.approaches/bitfield/content.md @@ -25,3 +25,48 @@ def is_isogram(phrase): return True ``` + +This solution uses the [ASCII][ascii] value of the letter to set the corresponding bit position. + +Some [constants][const] are defined for readability in the function. +Python doesn't _enforce_ having real constant values, but using all uppercase letters is the naming convention for a Python constant. +It indicates that the value is not intended to be changed. + +- The ASCII value for `a` is `97`. +- The ASCII value for `z` is `122`. +- The ASCII value for `A` is `65`. +- The ASCII value for `Z` is `90`. + + +- A [`for`][for] loop is used to iterate the characters in the input phrase. +- The [`ord()`][ord] function is used to get the ASCII value of the letter. +- The `if` statements look for a character being `a` through `z` or `A` through `Z`. + +- If the lowercase letter is subtracted by `97`, then `a` will result in `0`, because `97` minus `97` equals `0`. + `z` would result in `25`, because `122` minus `97` equals `25`. + So `a` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `z` would have `1` shifted left 25 places. +- If the uppercase letter is subtracted by `A`, then `A` will result in `0`, because `65` minus `65` equals `0`. + `Z` would result in `25`, because `90` minus `65` equals `25`. + So `A` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `Z` would have `1` shifted left 25 places. + +In that way, both a lower-cased `z` and an upper-cased `Z` can share the same position in the bit field. + +So, for a thirty-two bit integer, if the values for `a` and `Z` were both set, the bits would look like + +``` + zyxwvutsrqponmlkjihgfedcba +00000010000000000000000000000001 +``` + +We can use the [bitwise AND operator][and] to check if a bit has already been set. +If it has been set, we know the letter is duplicated and we can immediately return `False`. +If it has not been set, we can use the [bitwise OR operator][or] to set the bit. +If the loop completes without finding a duplicate letter (and returning `False`), the function returns `True`. + +[ascii]: https://www.asciitable.com/ +[const]: https://realpython.com/python-constants/ +[for]: https://realpython.com/python-for-loop/#the-python-for-loop +[ord]: https://docs.python.org/3/library/functions.html?#ord +[shift-left]: https://realpython.com/python-bitwise-operators/#left-shift +[or]: https://realpython.com/python-bitwise-operators/#bitwise-or +[and]: https://realpython.com/python-bitwise-operators/#bitwise-and From 1ed21677cc31e59bef7608d6d4c61905a4b32d45 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 04:21:34 -0600 Subject: [PATCH 122/826] Update content.md --- .../practice/isogram/.approaches/findall-regex/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/isogram/.approaches/findall-regex/content.md b/exercises/practice/isogram/.approaches/findall-regex/content.md index bc4a6f619a..72f6bb4164 100644 --- a/exercises/practice/isogram/.approaches/findall-regex/content.md +++ b/exercises/practice/isogram/.approaches/findall-regex/content.md @@ -19,8 +19,8 @@ This makes a string out of the list of matched characters. - The output of `join()` is then [chained][method-chaining] as the input for [`lower()`][lower]. All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, since `A` and `a` are considered to be the same letter. -When the filtering and lowercasing is done, the `scrubbed` variable will be a string having all alphabetic letters lowercased. -- A [`set`][set] is constructed from the `scrubbed` string and its [`len`][len] is compared with the `len` of the the `scrubbed` string. +When the filtering and lowercasing is done, the scrubbed variable will be a string having all alphabetic letters lowercased. +- A [`set`][set] is constructed from the scrubbed string and its [`len`][len] is compared with the `len` of the the scrubbed string. Since a `set` holds only unique values, the phrase will be an isogram if its number of unique letters is the same as its total number of letters. The function returns whether the number of unique letters equals the total number of letters. - For `Alpha` it would return `False`, because `a` is considered to repeat `A`, so the number of unique letters in `Alpha` is `4`, From 57d7b3a895b0d9a575e42c7e101a76836bb86be6 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 04:22:49 -0600 Subject: [PATCH 123/826] Update content.md --- .../isogram/.approaches/scrub-comprehension/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/isogram/.approaches/scrub-comprehension/content.md b/exercises/practice/isogram/.approaches/scrub-comprehension/content.md index 04dc5bf0e2..66e5bc7d03 100644 --- a/exercises/practice/isogram/.approaches/scrub-comprehension/content.md +++ b/exercises/practice/isogram/.approaches/scrub-comprehension/content.md @@ -17,8 +17,8 @@ since `A` and `a` are considered to be the same letter. - The letters are filtered by the use of the [optional conditional logic][conditional-logic]: `if ltr.isalpha()`. [`isalpha()`][isalpha] returns `True` if the letter being iterated is alphabetic. -When the list comprehension is done, the `scrubbed` variable will be a list holding only lowercased alphabetic characters. -- A [`set`][set] is constructed from the `scrubbed` list and its [`len`][len] is compared with the `len` of the the `scrubbed` list. +When the list comprehension is done, the scrubbed variable will be a list holding only lowercased alphabetic characters. +- A [`set`][set] is constructed from the scrubbed list and its [`len`][len] is compared with the `len` of the the scrubbed list. Since a `set` holds only unique values, the phrase will be an isogram if its number of unique letters is the same as its total number of letters. The function returns whether the number of unique letters equals the total number of letters. - For `Alpha` it would return `False`, because `a` is considered to repeat `A`, so the number of unique letters in `Alpha` is `4`, From 4dec58271fc30439c84d1727cd2f87baad637399 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 04:25:01 -0600 Subject: [PATCH 124/826] Update content.md --- exercises/practice/isogram/.approaches/scrub-regex/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/isogram/.approaches/scrub-regex/content.md b/exercises/practice/isogram/.approaches/scrub-regex/content.md index 0d2316c05b..7c9c1b0dfe 100644 --- a/exercises/practice/isogram/.approaches/scrub-regex/content.md +++ b/exercises/practice/isogram/.approaches/scrub-regex/content.md @@ -11,9 +11,9 @@ def is_isogram(phrase): ``` For this approach, [regular expression][regex], also known as a [regex][regex-how-to], is used to scrub the input phrase [str][str]ing. -- In the pattern of `[^a-zA-Z]` the brackets are used to define a character set that looks for characters which are not `a` through `z` and `A` through `Z`. +- In the pattern of `[^a-zA-Z]` the brackets are used to define a character set that looks for characters which are _not_ `a` through `z` and `A` through `Z`. ```exercism/note -If the first character of a character set is `^`, all the characters that are _not_ in the rest of the set will be matched. +If the first character of a character set is `^`, all the characters that are _not_ in the rest of the character set will be matched. ``` This essentially matches any characters which are not in the English alphabet. The pattern is passed to the [`compile()`][compile] method to construct a [regular expression object][regex-object]. From e0f35d0685163f5e43c1884157ed0fff13c10a1a Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 04:26:17 -0600 Subject: [PATCH 125/826] Update content.md --- .../practice/isogram/.approaches/scrub-replace/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/isogram/.approaches/scrub-replace/content.md b/exercises/practice/isogram/.approaches/scrub-replace/content.md index dd65e9ab8c..041727a8ee 100644 --- a/exercises/practice/isogram/.approaches/scrub-replace/content.md +++ b/exercises/practice/isogram/.approaches/scrub-replace/content.md @@ -12,9 +12,9 @@ Thw two `replace()` calls are [chained][method-chaining], so the output of the f The output of the last `replace()` is the input for [`lower()`][lower]. All of the letters are lowercased so that letters of different cases will become the same letter for comparison purposes, since `A` and `a` are considered to be the same letter. -When the replacing and lowercasing is done, the `scrubbed` variable will be a string having no hyphens or spaces, +When the replacing and lowercasing is done, the scrubbed variable will be a string having no hyphens or spaces, and with all alphabetic letters lowercased. -- A [`set`][set] is constructed from the `scrubbed` string and its [`len`][len] is compared with the `len` of the the `scrubbed` string. +- A [`set`][set] is constructed from the scrubbed string and its [`len`][len] is compared with the `len` of the the scrubbed string. Since a `set` holds only unique values, the phrase will be an isogram if its number of unique letters is the same as its total number of letters. The function returns whether the number of unique letters equals the total number of letters. - For `Alpha` it would return `False`, because `a` is considered to repeat `A`, so the number of unique letters in `Alpha` is `4`, From 7f9d017b6c7559aff609c1a85ce7932eeeb393f1 Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 17:33:00 +0100 Subject: [PATCH 126/826] Update roman_numeals --- exercises/practice/roman-numerals/.meta/tests.toml | 6 ++++++ exercises/practice/roman-numerals/roman_numerals_test.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/exercises/practice/roman-numerals/.meta/tests.toml b/exercises/practice/roman-numerals/.meta/tests.toml index 1f669c213e..ca142e9f91 100644 --- a/exercises/practice/roman-numerals/.meta/tests.toml +++ b/exercises/practice/roman-numerals/.meta/tests.toml @@ -80,3 +80,9 @@ description = "666 is DCLXVI" [efbe1d6a-9f98-4eb5-82bc-72753e3ac328] description = "1666 is MDCLXVI" + +[3bc4b41c-c2e6-49d9-9142-420691504336] +description = "3001 is MMMI" + +[4e18e96b-5fbb-43df-a91b-9cb511fe0856] +description = "3999 is MMMCMXCIX" diff --git a/exercises/practice/roman-numerals/roman_numerals_test.py b/exercises/practice/roman-numerals/roman_numerals_test.py index 49b74d987e..f85422cc5e 100644 --- a/exercises/practice/roman-numerals/roman_numerals_test.py +++ b/exercises/practice/roman-numerals/roman_numerals_test.py @@ -77,3 +77,9 @@ def test_666_is_dclxvi(self): def test_1666_is_mdclxvi(self): self.assertEqual(roman(1666), "MDCLXVI") + + def test_3001_is_mmmi(self): + self.assertEqual(roman(3001), "MMMI") + + def test_3999_is_mmmcmxcix(self): + self.assertEqual(roman(3999), "MMMCMXCIX") From fe875bf3dadbc9420d441e999df7fc688166066d Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 17:53:08 +0100 Subject: [PATCH 127/826] Updated series --- exercises/practice/series/.meta/tests.toml | 16 +++++++++++++--- exercises/practice/series/series_test.py | 8 ++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/exercises/practice/series/.meta/tests.toml b/exercises/practice/series/.meta/tests.toml index 52ff7ed54c..9696f51fca 100644 --- a/exercises/practice/series/.meta/tests.toml +++ b/exercises/practice/series/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [7ae7a46a-d992-4c2a-9c15-a112d125ebad] description = "slices of one from one" @@ -23,6 +30,9 @@ description = "slices of a long series" [6d235d85-46cf-4fae-9955-14b6efef27cd] description = "slice length is too large" +[d7957455-346d-4e47-8e4b-87ed1564c6d7] +description = "slice length is way too large" + [d34004ad-8765-4c09-8ba1-ada8ce776806] description = "slice length cannot be zero" diff --git a/exercises/practice/series/series_test.py b/exercises/practice/series/series_test.py index c4a6371255..7f6de7a466 100644 --- a/exercises/practice/series/series_test.py +++ b/exercises/practice/series/series_test.py @@ -37,6 +37,14 @@ def test_slice_length_is_too_large(self): err.exception.args[0], "slice length cannot be greater than series length" ) + def test_slice_length_is_way_too_large(self): + with self.assertRaises(ValueError) as err: + slices("12345", 42) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual( + err.exception.args[0], "slice length cannot be greater than series length" + ) + def test_slice_length_cannot_be_zero(self): with self.assertRaises(ValueError) as err: slices("12345", 0) From 57e71a4d71e80c5a9c8c8cc01cbedb489738d6bb Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 18:36:26 +0100 Subject: [PATCH 128/826] updated luhn --- exercises/practice/luhn/.meta/tests.toml | 3 +++ exercises/practice/luhn/luhn_test.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml index eea08d7976..c0be0c4d9d 100644 --- a/exercises/practice/luhn/.meta/tests.toml +++ b/exercises/practice/luhn/.meta/tests.toml @@ -33,6 +33,9 @@ description = "invalid credit card" [20e67fad-2121-43ed-99a8-14b5b856adb9] description = "invalid long number with an even remainder" +[7e7c9fc1-d994-457c-811e-d390d52fba5e] +description = "invalid long number with a remainder divisible by 5" + [ad2a0c5f-84ed-4e5b-95da-6011d6f4f0aa] description = "valid number with an even number of digits" diff --git a/exercises/practice/luhn/luhn_test.py b/exercises/practice/luhn/luhn_test.py index a60d8525f2..bbc1f8eb1f 100644 --- a/exercises/practice/luhn/luhn_test.py +++ b/exercises/practice/luhn/luhn_test.py @@ -32,6 +32,9 @@ def test_invalid_credit_card(self): def test_invalid_long_number_with_an_even_remainder(self): self.assertIs(Luhn("1 2345 6789 1234 5678 9012").valid(), False) + def test_invalid_long_number_with_a_remainder_divisible_by_5(self): + self.assertIs(Luhn("1 2345 6789 1234 5678 9013").valid(), False) + def test_valid_number_with_an_even_number_of_digits(self): self.assertIs(Luhn("095 245 88").valid(), True) From e19101e7c20741e5b3899133ea222a482232c70e Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 18:39:59 +0100 Subject: [PATCH 129/826] updated word_count_test --- exercises/practice/word-count/.meta/tests.toml | 3 +++ exercises/practice/word-count/word_count_test.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/exercises/practice/word-count/.meta/tests.toml b/exercises/practice/word-count/.meta/tests.toml index 247f13746f..1be425b33c 100644 --- a/exercises/practice/word-count/.meta/tests.toml +++ b/exercises/practice/word-count/.meta/tests.toml @@ -52,3 +52,6 @@ description = "multiple spaces not detected as a word" [50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360] description = "alternating word separators not detected as a word" + +[6d00f1db-901c-4bec-9829-d20eb3044557] +description = "quotation for word with apostrophe" diff --git a/exercises/practice/word-count/word_count_test.py b/exercises/practice/word-count/word_count_test.py index 54af5f230a..b63a9eb357 100644 --- a/exercises/practice/word-count/word_count_test.py +++ b/exercises/practice/word-count/word_count_test.py @@ -88,6 +88,9 @@ def test_alternating_word_separators_not_detected_as_a_word(self): count_words(",\n,one,\n ,two \n 'three'"), {"one": 1, "two": 1, "three": 1} ) + def test_quotation_for_word_with_apostrophe(self): + self.assertEqual(count_words("can, can't, 'can't'"), {"can": 1, "can't": 2}) + # Additional tests for this track def test_tabs(self): From f76bac8aaf1478318e0d3c8dfc17d067644f8f33 Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 18:52:28 +0100 Subject: [PATCH 130/826] Updated matching_brackets_test --- exercises/practice/matching-brackets/.meta/tests.toml | 3 +++ exercises/practice/matching-brackets/matching_brackets_test.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/exercises/practice/matching-brackets/.meta/tests.toml b/exercises/practice/matching-brackets/.meta/tests.toml index da1314742a..35a98a0421 100644 --- a/exercises/practice/matching-brackets/.meta/tests.toml +++ b/exercises/practice/matching-brackets/.meta/tests.toml @@ -48,6 +48,9 @@ description = "unpaired and nested brackets" [a0205e34-c2ac-49e6-a88a-899508d7d68e] description = "paired and wrong nested brackets" +[1d5c093f-fc84-41fb-8c2a-e052f9581602] +description = "paired and wrong nested brackets but innermost are correct" + [ef47c21b-bcfd-4998-844c-7ad5daad90a8] description = "paired and incomplete brackets" diff --git a/exercises/practice/matching-brackets/matching_brackets_test.py b/exercises/practice/matching-brackets/matching_brackets_test.py index 86b429d2e0..fd23bd7840 100644 --- a/exercises/practice/matching-brackets/matching_brackets_test.py +++ b/exercises/practice/matching-brackets/matching_brackets_test.py @@ -47,6 +47,9 @@ def test_unpaired_and_nested_brackets(self): def test_paired_and_wrong_nested_brackets(self): self.assertEqual(is_paired("[({]})"), False) + def test_paired_and_wrong_nested_brackets_but_innermost_are_correct(self): + self.assertEqual(is_paired("[({}])"), False) + def test_paired_and_incomplete_brackets(self): self.assertEqual(is_paired("{}["), False) From efe7a6db35f59613546c5b8e58c699f925b65ff3 Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 19:29:26 +0100 Subject: [PATCH 131/826] updated rectangles_test --- exercises/practice/rectangles/.meta/tests.toml | 16 +++++++++++++--- exercises/practice/rectangles/rectangles_test.py | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/exercises/practice/rectangles/.meta/tests.toml b/exercises/practice/rectangles/.meta/tests.toml index 63cd6c4d9a..282015033a 100644 --- a/exercises/practice/rectangles/.meta/tests.toml +++ b/exercises/practice/rectangles/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [485b7bab-4150-40aa-a8db-73013427d08c] description = "no rows" @@ -40,3 +47,6 @@ description = "corner is required for a rectangle to be complete" [d78fe379-8c1b-4d3c-bdf7-29bfb6f6dc66] description = "large input with many rectangles" + +[6ef24e0f-d191-46da-b929-4faca24b4cd2] +description = "rectangles must have four sides" diff --git a/exercises/practice/rectangles/rectangles_test.py b/exercises/practice/rectangles/rectangles_test.py index 3cfa8181cf..59c622de76 100644 --- a/exercises/practice/rectangles/rectangles_test.py +++ b/exercises/practice/rectangles/rectangles_test.py @@ -83,6 +83,22 @@ def test_large_input_with_many_rectangles(self): 60, ) + def test_rectangles_must_have_four_sides(self): + self.assertEqual( + rectangles( + [ + "+-+ +-+", + "| | | |", + "+-+-+-+", + " | | ", + "+-+-+-+", + "| | | |", + "+-+ +-+", + ] + ), + 5, + ) + if __name__ == "__main__": unittest.main() From 7b57a24ab1cdc8f5b0d2a2012b04b21058d38fae Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 17:39:30 +0100 Subject: [PATCH 132/826] Added new_test files --- exercises/practice/say/.meta/tests.toml | 25 ++++++++++++++++++++++--- exercises/practice/say/say_test.py | 12 ++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/exercises/practice/say/.meta/tests.toml b/exercises/practice/say/.meta/tests.toml index df50fd17bb..a5532e9ed3 100644 --- a/exercises/practice/say/.meta/tests.toml +++ b/exercises/practice/say/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [5d22a120-ba0c-428c-bd25-8682235d83e8] description = "zero" @@ -17,12 +24,24 @@ description = "twenty" [d78601eb-4a84-4bfa-bf0e-665aeb8abe94] description = "twenty-two" +[f010d4ca-12c9-44e9-803a-27789841adb1] +description = "thirty" + +[738ce12d-ee5c-4dfb-ad26-534753a98327] +description = "ninety-nine" + [e417d452-129e-4056-bd5b-6eb1df334dce] description = "one hundred" [d6924f30-80ba-4597-acf6-ea3f16269da8] description = "one hundred twenty-three" +[2f061132-54bc-4fd4-b5df-0a3b778959b9] +description = "two hundred" + +[feed6627-5387-4d38-9692-87c0dbc55c33] +description = "nine hundred ninety-nine" + [3d83da89-a372-46d3-b10d-de0c792432b3] description = "one thousand" diff --git a/exercises/practice/say/say_test.py b/exercises/practice/say/say_test.py index 0d14b6e7cd..22deb5364c 100644 --- a/exercises/practice/say/say_test.py +++ b/exercises/practice/say/say_test.py @@ -23,12 +23,24 @@ def test_twenty(self): def test_twenty_two(self): self.assertEqual(say(22), "twenty-two") + def test_thirty(self): + self.assertEqual(say(30), "thirty") + + def test_ninety_nine(self): + self.assertEqual(say(99), "ninety-nine") + def test_one_hundred(self): self.assertEqual(say(100), "one hundred") def test_one_hundred_twenty_three(self): self.assertEqual(say(123), "one hundred twenty-three") + def test_two_hundred(self): + self.assertEqual(say(200), "two hundred") + + def test_nine_hundred_ninety_nine(self): + self.assertEqual(say(999), "nine hundred ninety-nine") + def test_one_thousand(self): self.assertEqual(say(1000), "one thousand") From cca3640d5304d83308b1c86ebb8a32b790e595fc Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 18:44:15 +0100 Subject: [PATCH 133/826] Updated crypto_square_test --- .../practice/crypto-square/.meta/tests.toml | 16 +++++++++++++--- .../practice/crypto-square/crypto_square_test.py | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/exercises/practice/crypto-square/.meta/tests.toml b/exercises/practice/crypto-square/.meta/tests.toml index 054544573b..085d142ead 100644 --- a/exercises/practice/crypto-square/.meta/tests.toml +++ b/exercises/practice/crypto-square/.meta/tests.toml @@ -1,10 +1,20 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [407c3837-9aa7-4111-ab63-ec54b58e8e9f] description = "empty plaintext results in an empty ciphertext" +[aad04a25-b8bb-4304-888b-581bea8e0040] +description = "normalization results in empty plaintext" + [64131d65-6fd9-4f58-bdd8-4a2370fb481d] description = "Lowercase" diff --git a/exercises/practice/crypto-square/crypto_square_test.py b/exercises/practice/crypto-square/crypto_square_test.py index 4829f90565..3715400ca3 100644 --- a/exercises/practice/crypto-square/crypto_square_test.py +++ b/exercises/practice/crypto-square/crypto_square_test.py @@ -13,6 +13,11 @@ def test_empty_plaintext_results_in_an_empty_ciphertext(self): expected = "" self.assertEqual(cipher_text(value), expected) + def test_normalization_results_in_empty_plaintext(self): + value = "... --- ..." + expected = "" + self.assertEqual(cipher_text(value), expected) + def test_lowercase(self): value = "A" expected = "a" From 2638645fc9036b8f2e9d9c6045f60edcb691cbe4 Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 19:38:07 +0100 Subject: [PATCH 134/826] [Maintance] Updated complex_numbers_test --- exercises/practice/complex-numbers/.meta/tests.toml | 3 +++ exercises/practice/complex-numbers/complex_numbers_test.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/exercises/practice/complex-numbers/.meta/tests.toml b/exercises/practice/complex-numbers/.meta/tests.toml index 1b36ba3b79..dffb1f2a31 100644 --- a/exercises/practice/complex-numbers/.meta/tests.toml +++ b/exercises/practice/complex-numbers/.meta/tests.toml @@ -102,6 +102,9 @@ description = "Complex exponential function -> Exponential of a purely real numb [08eedacc-5a95-44fc-8789-1547b27a8702] description = "Complex exponential function -> Exponential of a number with real and imaginary part" +[d2de4375-7537-479a-aa0e-d474f4f09859] +description = "Complex exponential function -> Exponential resulting in a number with real and imaginary part" + [06d793bf-73bd-4b02-b015-3030b2c952ec] description = "Operations between real numbers and complex numbers -> Add real number to complex number" diff --git a/exercises/practice/complex-numbers/complex_numbers_test.py b/exercises/practice/complex-numbers/complex_numbers_test.py index 7580468d1d..2eca784969 100644 --- a/exercises/practice/complex-numbers/complex_numbers_test.py +++ b/exercises/practice/complex-numbers/complex_numbers_test.py @@ -148,6 +148,11 @@ def test_exponential_of_a_number_with_real_and_imaginary_part(self): ComplexNumber(math.log(2), math.pi).exp(), ComplexNumber(-2, 0) ) + def test_exponential_resulting_in_a_number_with_real_and_imaginary_part(self): + self.assertAlmostEqual( + ComplexNumber(math.log(2) / 2, math.pi / 4).exp(), ComplexNumber(1, 1) + ) + # Operations between real numbers and complex numbers def test_add_real_number_to_complex_number(self): From 8d5600de97a79c3c0ce4144a9517f7fa66eda137 Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 19:43:44 +0100 Subject: [PATCH 135/826] [Mantaince] Updated dominoes_test --- exercises/practice/dominoes/.meta/tests.toml | 16 +++++++++++++--- exercises/practice/dominoes/dominoes_test.py | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/exercises/practice/dominoes/.meta/tests.toml b/exercises/practice/dominoes/.meta/tests.toml index 23ff84f906..08c8e08d02 100644 --- a/exercises/practice/dominoes/.meta/tests.toml +++ b/exercises/practice/dominoes/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [31a673f2-5e54-49fe-bd79-1c1dae476c9c] description = "empty input = empty output" @@ -37,3 +44,6 @@ description = "separate loops" [cd061538-6046-45a7-ace9-6708fe8f6504] description = "nine elements" + +[44704c7c-3adb-4d98-bd30-f45527cf8b49] +description = "separate three-domino loops" diff --git a/exercises/practice/dominoes/dominoes_test.py b/exercises/practice/dominoes/dominoes_test.py index 3f0b289908..a62f130cc2 100644 --- a/exercises/practice/dominoes/dominoes_test.py +++ b/exercises/practice/dominoes/dominoes_test.py @@ -78,6 +78,11 @@ def test_nine_elements(self): output_chain = can_chain(input_dominoes) self.assert_correct_chain(input_dominoes, output_chain) + def test_separate_three_domino_loops(self): + input_dominoes = [(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)] + output_chain = can_chain(input_dominoes) + self.refute_correct_chain(input_dominoes, output_chain) + # Utility methods def normalize_dominoes(self, dominoes): From ee283023b51ffac586a3cd475c6c2e4c08025575 Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 17:26:31 +0100 Subject: [PATCH 136/826] update_giga_second --- exercises/practice/gigasecond/.meta/tests.toml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/exercises/practice/gigasecond/.meta/tests.toml b/exercises/practice/gigasecond/.meta/tests.toml index 18672327f3..a7caf00dbc 100644 --- a/exercises/practice/gigasecond/.meta/tests.toml +++ b/exercises/practice/gigasecond/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [92fbe71c-ea52-4fac-bd77-be38023cacf7] description = "date only specification of time" @@ -16,3 +23,6 @@ description = "full time specified" [09d4e30e-728a-4b52-9005-be44a58d9eba] description = "full time with day roll-over" + +[fcec307c-7529-49ab-b0fe-20309197618a] +description = "does not mutate the input" From dbd81f0205b8a411854546feab4c28b1efb085a4 Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 20:05:50 +0100 Subject: [PATCH 137/826] [Mantaince] Updated gigasecond_test --- exercises/practice/gigasecond/.meta/tests.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/gigasecond/.meta/tests.toml b/exercises/practice/gigasecond/.meta/tests.toml index a7caf00dbc..ef84c8666b 100644 --- a/exercises/practice/gigasecond/.meta/tests.toml +++ b/exercises/practice/gigasecond/.meta/tests.toml @@ -26,3 +26,4 @@ description = "full time with day roll-over" [fcec307c-7529-49ab-b0fe-20309197618a] description = "does not mutate the input" +include = false \ No newline at end of file From caa255acca787f093842d085a36331f96bfcafd8 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Fri, 2 Dec 2022 21:32:04 +0100 Subject: [PATCH 138/826] Update config.json --- exercises/concept/tisbury-treasure-hunt/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/tisbury-treasure-hunt/.meta/config.json b/exercises/concept/tisbury-treasure-hunt/.meta/config.json index e806bfb846..3a8c476fe9 100644 --- a/exercises/concept/tisbury-treasure-hunt/.meta/config.json +++ b/exercises/concept/tisbury-treasure-hunt/.meta/config.json @@ -1,6 +1,6 @@ { "blurb": "Learn about tuples by helping out competitors in the Tisbury Treasure Hunt.", - "icon": "proverb", + "icon": "tisbury-treasure-hunt", "authors": ["bethanyg"], "files": { "solution": ["tuples.py"], From add5e7c824149691d0a930f8ac788cf74c59ae6c Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 29 Nov 2022 22:17:55 +0100 Subject: [PATCH 139/826] fixes --- exercises/concept/locomotive-engineer/.docs/instructions.md | 4 ++-- exercises/concept/locomotive-engineer/.meta/config.json | 1 + exercises/concept/locomotive-engineer/locomotive_engineer.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md index c81cedcff7..b24ae484ec 100644 --- a/exercises/concept/locomotive-engineer/.docs/instructions.md +++ b/exercises/concept/locomotive-engineer/.docs/instructions.md @@ -19,7 +19,7 @@ The function should then `return` the given IDs as a single `list`. ```python >>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5) -[1, 7, 12, 3, 14, 8, 3] +[1, 7, 12, 3, 14, 8, 5] ``` ## 2. Fix the list of wagons @@ -41,7 +41,7 @@ The function should then `return` a `list` with the modifications. ```python >>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15]) -[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5] +[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5] ``` ## 3. Add missing stops diff --git a/exercises/concept/locomotive-engineer/.meta/config.json b/exercises/concept/locomotive-engineer/.meta/config.json index 86a8a8b6a3..4c10c88263 100644 --- a/exercises/concept/locomotive-engineer/.meta/config.json +++ b/exercises/concept/locomotive-engineer/.meta/config.json @@ -2,6 +2,7 @@ "blurb": "Learn about unpacking and multiple assignment in Python while helping Linus with his train control system.", "icon": "tracks-on-tracks-on-tracks", "authors": ["meatball133","BethanyG"], + "contributors": ["IsaacG"], "files": { "solution": ["locomotive_engineer.py"], "test": ["locomotive_engineer_test.py"], diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer.py b/exercises/concept/locomotive-engineer/locomotive_engineer.py index 4166a24b68..d9291f65a3 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer.py @@ -48,7 +48,7 @@ def extend_route_information(route, more_route_information): def fix_wagon_depot(wagons_rows): """Fix the list of rows of wagons. - :param wagons_rows: list[tuple] - the list of rows of wagons. - :return: list[tuple] - list of rows of wagons. + :param wagons_rows: list[list[tuple]] - the list of rows of wagons. + :return: list[list[tuple]] - list of rows of wagons. """ pass From 5b36121729c722674628235e17f65bc5cd02266c Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 29 Nov 2022 22:21:52 +0100 Subject: [PATCH 140/826] Update instructions.md --- .../locomotive-engineer/.docs/instructions.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md index b24ae484ec..af914e4924 100644 --- a/exercises/concept/locomotive-engineer/.docs/instructions.md +++ b/exercises/concept/locomotive-engineer/.docs/instructions.md @@ -102,14 +102,14 @@ Your function should return a `list` with the three "row" `lists` reordered to h ```python >>> fix_wagon_depot([ - [(2, "red"), (4, "red"),(8, "red")], - [(5, "blue"),(9, "blue"),(13,"blue")], - [(3, "orange"),(7, "orange"), (11, "orange")], + [(2, "red"), (4, "red"), (8, "red")], + [(5, "blue"), (9, "blue"), (13,"blue")], + [(3, "orange"), (7, "orange"), (11, "orange")], ]) [ -[(2, "red"),(5, "blue"),(3, "orange")], -[(4, "red"),(9, "blue"),(7, "orange")], -[(8, "red"),(13,"blue"),(11, "orange")] +[(2, "red"), (5, "blue"), (3, "orange")], +[(4, "red"), (9, "blue"), (7, "orange")], +[(8, "red"), (13,"blue"), (11, "orange")] ] ``` From 23ab8d8b528fdcda3bb61c1984ab3b08375bdd55 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 29 Nov 2022 22:24:06 +0100 Subject: [PATCH 141/826] Update about.md --- concepts/unpacking-and-multiple-assignment/about.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 5623b2f034..e8abaa528d 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -259,7 +259,7 @@ Usage of `*args`: # This function is defined to take any number of positional arguments >>> def my_function(*args): -... print(args) +... print(args) # Arguments given to the function are packed into a tuple @@ -328,8 +328,8 @@ If you don't follow this order then you will get an error. Writing arguments in an incorrect order will result in an error: ```python ->>>def my_function(*args, a, b): -... print(args) +>>> def my_function(*args, a, b): +... print(args) >>>my_function(1, 2, 3, 4, 5) Traceback (most recent call last): From db28e5829170afab872b5e6af5efc70415733107 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 11:47:54 +0100 Subject: [PATCH 142/826] Added resistor_color_trio_test --- config.json | 14 +++++ .../resistor-color-trio/.docs/instructions.md | 54 +++++++++++++++++++ .../resistor-color-trio/.meta/config.json | 22 ++++++++ .../resistor-color-trio/.meta/example.py | 18 +++++++ .../resistor-color-trio/.meta/template.j2 | 15 ++++++ .../resistor-color-trio/.meta/tests.toml | 25 +++++++++ .../resistor_color_trio.py | 2 + .../resistor_color_trio_test.py | 24 +++++++++ 8 files changed, 174 insertions(+) create mode 100644 exercises/practice/resistor-color-trio/.docs/instructions.md create mode 100644 exercises/practice/resistor-color-trio/.meta/config.json create mode 100644 exercises/practice/resistor-color-trio/.meta/example.py create mode 100644 exercises/practice/resistor-color-trio/.meta/template.j2 create mode 100644 exercises/practice/resistor-color-trio/.meta/tests.toml create mode 100644 exercises/practice/resistor-color-trio/resistor_color_trio.py create mode 100644 exercises/practice/resistor-color-trio/resistor_color_trio_test.py diff --git a/config.json b/config.json index 7350c2d7c8..8735e24764 100644 --- a/config.json +++ b/config.json @@ -682,6 +682,20 @@ ], "difficulty": 2 }, + { + "slug": "resistor-color-trio", + "name": "Resistor Color Trio", + "uuid": "089f06a6-0759-479c-8c00-d699525a1e22", + "practices": ["list-methods"], + "prerequisites": [ + "basics", + "bools", + "lists", + "list-methods", + "numbers" + ], + "difficulty": 2 + }, { "slug": "twelve-days", "name": "Twelve Days", diff --git a/exercises/practice/resistor-color-trio/.docs/instructions.md b/exercises/practice/resistor-color-trio/.docs/instructions.md new file mode 100644 index 0000000000..fcc76958a5 --- /dev/null +++ b/exercises/practice/resistor-color-trio/.docs/instructions.md @@ -0,0 +1,54 @@ +# Instructions + +If you want to build something using a Raspberry Pi, you'll probably use _resistors_. +For this exercise, you need to know only three things about them: + +- Each resistor has a resistance value. +- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. + To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. +- Each band acts as a digit of a number. + For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. + In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. + The program will take 3 colors as input, and outputs the correct value, in ohms. + The color bands are encoded as follows: + +- Black: 0 +- Brown: 1 +- Red: 2 +- Orange: 3 +- Yellow: 4 +- Green: 5 +- Blue: 6 +- Violet: 7 +- Grey: 8 +- White: 9 + +In `resistor-color duo` you decoded the first two colors. +For instance: orange-orange got the main value `33`. +The third color stands for how many zeros need to be added to the main value. +The main value plus the zeros gives us a value in ohms. +For the exercise it doesn't matter what ohms really are. +For example: + +- orange-orange-black would be 33 and no zeros, which becomes 33 ohms. +- orange-orange-red would be 33 and 2 zeros, which becomes 3300 ohms. +- orange-orange-orange would be 33 and 3 zeros, which becomes 33000 ohms. + +(If Math is your thing, you may want to think of the zeros as exponents of 10. +If Math is not your thing, go with the zeros. +It really is the same thing, just in plain English instead of Math lingo.) + +This exercise is about translating the colors into a label: + +> "... ohms" + +So an input of `"orange", "orange", "black"` should return: + +> "33 ohms" + +When we get more than a thousand ohms, we say "kiloohms". +That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. + +So an input of `"orange", "orange", "orange"` should return: + +> "33 kiloohms" diff --git a/exercises/practice/resistor-color-trio/.meta/config.json b/exercises/practice/resistor-color-trio/.meta/config.json new file mode 100644 index 0000000000..9dfb34d6ba --- /dev/null +++ b/exercises/practice/resistor-color-trio/.meta/config.json @@ -0,0 +1,22 @@ +{ + "blurb": "Convert color codes, as used on resistors, to a human-readable label.", + "authors": [ + "meabtall133", + "bethanyg" + ], + "contributors": [ + ], + "files": { + "solution": [ + "resistor_color_trio.py" + ], + "test": [ + "resistor_color_trio_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "source": "Maud de Vries, Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/issues/1549" +} diff --git a/exercises/practice/resistor-color-trio/.meta/example.py b/exercises/practice/resistor-color-trio/.meta/example.py new file mode 100644 index 0000000000..a6588ea5a6 --- /dev/null +++ b/exercises/practice/resistor-color-trio/.meta/example.py @@ -0,0 +1,18 @@ +COLORS = [ + 'black', + 'brown', + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'violet', + 'grey', + 'white' +] + + +def label(colors): + value = 10 * COLORS.index(colors[0]) + COLORS.index(colors[1]) + value *= 10 ** COLORS.index(colors[2]) + return str(value) + ' ohms' if value < 1000 else str(value // 1000) + ' kiloohms' diff --git a/exercises/practice/resistor-color-trio/.meta/template.j2 b/exercises/practice/resistor-color-trio/.meta/template.j2 new file mode 100644 index 0000000000..54814bed31 --- /dev/null +++ b/exercises/practice/resistor-color-trio/.meta/template.j2 @@ -0,0 +1,15 @@ +{%- import "generator_macros.j2" as macros with context -%} +{% macro test_case(case) -%} + {%- set input = case["input"] -%} + def test_{{ case["description"] | to_snake }}(self): + self.assertEqual( + {{ case["property"] | to_snake }}({{ case["input"]["colors"] }}), + "{{ case['expected']['value']}} {{ case['expected']['unit']}}" + ) +{%- endmacro %} +{{ macros.header()}} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + {{ test_case(case) }} + {% endfor %} diff --git a/exercises/practice/resistor-color-trio/.meta/tests.toml b/exercises/practice/resistor-color-trio/.meta/tests.toml new file mode 100644 index 0000000000..dc6077e54f --- /dev/null +++ b/exercises/practice/resistor-color-trio/.meta/tests.toml @@ -0,0 +1,25 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d6863355-15b7-40bb-abe0-bfb1a25512ed] +description = "Orange and orange and black" + +[1224a3a9-8c8e-4032-843a-5224e04647d6] +description = "Blue and grey and brown" + +[b8bda7dc-6b95-4539-abb2-2ad51d66a207] +description = "Red and black and red" + +[5b1e74bc-d838-4eda-bbb3-eaba988e733b] +description = "Green and brown and orange" + +[f5d37ef9-1919-4719-a90d-a33c5a6934c9] +description = "Yellow and violet and yellow" diff --git a/exercises/practice/resistor-color-trio/resistor_color_trio.py b/exercises/practice/resistor-color-trio/resistor_color_trio.py new file mode 100644 index 0000000000..1d36841cf1 --- /dev/null +++ b/exercises/practice/resistor-color-trio/resistor_color_trio.py @@ -0,0 +1,2 @@ +def label(colors): + pass diff --git a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py new file mode 100644 index 0000000000..302df179ef --- /dev/null +++ b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py @@ -0,0 +1,24 @@ +import unittest + +from resistor_color_trio import ( + label, +) + +# Tests adapted from `problem-specifications//canonical-data.json` + + +class ResistorColorTrioTest(unittest.TestCase): + def test_orange_and_orange_and_black(self): + self.assertEqual(label(["orange", "orange", "black"]), "33 ohms") + + def test_blue_and_grey_and_brown(self): + self.assertEqual(label(["blue", "grey", "brown"]), "680 ohms") + + def test_red_and_black_and_red(self): + self.assertEqual(label(["red", "black", "red"]), "2 kiloohms") + + def test_green_and_brown_and_orange(self): + self.assertEqual(label(["green", "brown", "orange"]), "51 kiloohms") + + def test_yellow_and_violet_and_yellow(self): + self.assertEqual(label(["yellow", "violet", "yellow"]), "470 kiloohms") From 9a4e2b6cf63d4a2db5a03c8901e4bb6e72712784 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 11:49:11 +0100 Subject: [PATCH 143/826] Updated the config --- config.json | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/config.json b/config.json index 8735e24764..5b5c101935 100644 --- a/config.json +++ b/config.json @@ -624,6 +624,21 @@ ], "difficulty": 2 }, + { + "slug": "resistor-color-trio", + "name": "Resistor Color Trio", + "uuid": "089f06a6-0759-479c-8c00-d699525a1e22", + "practices": ["list-methods"], + "prerequisites": [ + "basics", + "bools", + "lists", + "numbers", + "strings", + "comparisons" + ], + "difficulty": 2 + }, { "slug": "word-count", "name": "Word Count", @@ -682,20 +697,6 @@ ], "difficulty": 2 }, - { - "slug": "resistor-color-trio", - "name": "Resistor Color Trio", - "uuid": "089f06a6-0759-479c-8c00-d699525a1e22", - "practices": ["list-methods"], - "prerequisites": [ - "basics", - "bools", - "lists", - "list-methods", - "numbers" - ], - "difficulty": 2 - }, { "slug": "twelve-days", "name": "Twelve Days", From 9ea00e63c8ceccd913d7f39b2476cc76f8e46ee2 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 13:01:21 +0100 Subject: [PATCH 144/826] Fix uuid --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 5b5c101935..df8633bb8f 100644 --- a/config.json +++ b/config.json @@ -627,7 +627,7 @@ { "slug": "resistor-color-trio", "name": "Resistor Color Trio", - "uuid": "089f06a6-0759-479c-8c00-d699525a1e22", + "uuid": "f987a5b7-96f2-49c2-99e8-aef30d23dd58", "practices": ["list-methods"], "prerequisites": [ "basics", From 37178d980eb244d2856b9f8ec83e93252caf3d28 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sat, 3 Dec 2022 21:54:23 +0100 Subject: [PATCH 145/826] fix --- exercises/practice/resistor-color-trio/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/resistor-color-trio/.meta/config.json b/exercises/practice/resistor-color-trio/.meta/config.json index 9dfb34d6ba..600b6cc227 100644 --- a/exercises/practice/resistor-color-trio/.meta/config.json +++ b/exercises/practice/resistor-color-trio/.meta/config.json @@ -1,7 +1,7 @@ { "blurb": "Convert color codes, as used on resistors, to a human-readable label.", "authors": [ - "meabtall133", + "meatball133", "bethanyg" ], "contributors": [ From a0385ee2d353a518f17dcba5a629bf954eda25c4 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Mon, 28 Nov 2022 07:24:50 -0600 Subject: [PATCH 146/826] Create config.json --- exercises/practice/wordy/.approaches/config.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 exercises/practice/wordy/.approaches/config.json diff --git a/exercises/practice/wordy/.approaches/config.json b/exercises/practice/wordy/.approaches/config.json new file mode 100644 index 0000000000..ed71a84650 --- /dev/null +++ b/exercises/practice/wordy/.approaches/config.json @@ -0,0 +1,15 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "d643e2b4-daee-422d-b8d3-2cad2f439db5", + "slug": "dunder-getattribute", + "title": "dunder with __getattribute__", + "blurb": "Use dunder methods with __getattribute__.", + "authors": ["bobahop"] + } + ] +} From 00535db967ac68a6dcc9b8266b5fbae34d40b9de Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Mon, 28 Nov 2022 07:56:55 -0600 Subject: [PATCH 147/826] Create introduction.md --- .../wordy/.approaches/introduction.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 exercises/practice/wordy/.approaches/introduction.md diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md new file mode 100644 index 0000000000..7dcb96cb1e --- /dev/null +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -0,0 +1,56 @@ +# Introduction + +There are various ways to solve Wordy. +Using [`eval`][eval] is a [convenient but dangerous][eval-danger] approach. +Another approach could replace the operation words with [dunder][dunder] methods. +They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. +They are also called magic methods. +The dunder methods can be called by using the [`__getattribute__`][getattribute] method for [`int`][int]. + +## General guidance + +Parsing should verify that the expression in words can be translated to a valid mathematical expression. + +## Approach: Dunder methods with `__getattribute__` + +```python +OPS = { + "plus": "__add__", + "minus": "__sub__", + "multiplied by": "__mul__", + "divided by": "__truediv__" +} + + +def answer(question): + question = question.removeprefix("What is").removesuffix("?").strip() + if not question: raise ValueError("syntax error") + if question.isdigit(): return int(question) + + foundOp = False + for name, op in OPS.items(): + if name in question: + question = question.replace(name, op) + foundOp = True + if not foundOp: raise ValueError("unknown operation") + + ret = question.split() + while len(ret) > 1: + try: + x, op, y, *tail = ret + if op not in OPS.values(): raise ValueError("syntax error") + ret = [int(x).__getattribute__(op)(int(y)), *tail] + except: + raise ValueError("syntax error") + return ret[0] + +``` + +For more information, check the [dunder method with `__getattribute__` approach][approach-dunder-getattribute]. + +[eval]: https://docs.python.org/3/library/functions.html?#eval +[eval-danger]: https://diveintopython3.net/advanced-iterators.html#eval +[dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python +[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ +[int]: https://docs.python.org/3/library/functions.html?#int +[approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute From b75a3350e39ccba3bda963eef0b89d8626b28c79 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Mon, 28 Nov 2022 07:59:34 -0600 Subject: [PATCH 148/826] Create snippet.md --- .../wordy/.approaches/dunder-getattribute/snippet.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 exercises/practice/wordy/.approaches/dunder-getattribute/snippet.md diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.md b/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.md new file mode 100644 index 0000000000..c3aeda2fe9 --- /dev/null +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.md @@ -0,0 +1,10 @@ +```python +while len(ret) > 1: + try: + x, op, y, *tail = ret + if op not in OPS.values(): raise ValueError("syntax error") + ret = [int(x).__getattribute__(op)(int(y)), *tail] + except: + raise ValueError("syntax error") +return ret[0] +``` From f93f8867a7d1fb673ab60bb01b79456527444fa4 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:03:23 -0600 Subject: [PATCH 149/826] Update introduction.md --- exercises/practice/wordy/.approaches/introduction.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 7dcb96cb1e..99a5d5775f 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -1,10 +1,14 @@ # Introduction There are various ways to solve Wordy. -Using [`eval`][eval] is a [convenient but dangerous][eval-danger] approach. +Using [`eval`][eval] is a [convenient but potentially dangerous][eval-danger] approach. Another approach could replace the operation words with [dunder][dunder] methods. + +```exercism/note They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. They are also called magic methods. +``` + The dunder methods can be called by using the [`__getattribute__`][getattribute] method for [`int`][int]. ## General guidance From f5b593d8633b07dc310f0deeb4b2cec3c32bd448 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:18:42 -0600 Subject: [PATCH 150/826] Update introduction.md --- exercises/practice/wordy/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 99a5d5775f..32d6a92038 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -56,5 +56,5 @@ For more information, check the [dunder method with `__getattribute__` approach] [eval-danger]: https://diveintopython3.net/advanced-iterators.html#eval [dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python [getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ -[int]: https://docs.python.org/3/library/functions.html?#int +[int]: https://docs.python.org/3/library/stdtypes.html#typesnumeric [approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute From 98ba0439f6801f4dd7f000808122a706a1e0a5eb Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:32:12 -0600 Subject: [PATCH 151/826] Create content.md Not finished. Saving work... --- .../dunder-getattribute/content.md | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 exercises/practice/wordy/.approaches/dunder-getattribute/content.md diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md new file mode 100644 index 0000000000..70df27c450 --- /dev/null +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -0,0 +1,82 @@ +# Dunder methods with `__getattribute__` + +```python +OPS = { + "plus": "__add__", + "minus": "__sub__", + "multiplied by": "__mul__", + "divided by": "__truediv__" +} + + +def answer(question): + question = question.removeprefix("What is").removesuffix("?").strip() + if not question: raise ValueError("syntax error") + if question.isdigit(): return int(question) + + foundOp = False + for name, op in OPS.items(): + if name in question: + question = question.replace(name, op) + foundOp = True + if not foundOp: raise ValueError("unknown operation") + + ret = question.split() + while len(ret) > 1: + try: + x, op, y, *tail = ret + if op not in OPS.values(): raise ValueError("syntax error") + ret = [int(x).__getattribute__(op)(int(y)), *tail] + except: + raise ValueError("syntax error") + return ret[0] + +``` + +This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [dunder][dunder] methods. + +```exercism/note +They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. +They are also called magic methods. +``` + +Since only whole numbers are involved, the dunder methods are those for [`int`][int]. +The supported methods for `int` can be found by using `print(dir(int))`. +The built-in [`dir`][dir] function returns a list of valid attributes for an object. + +Python doesn't _enforce_ having real constant values, +but the `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. +It indicates that the value is not intended to be changed. + +The input question to the `answer` function is cleaned using the [`removeprefix`][removeprefix], [`removesuffix`][removesuffix], and [`strip`][strip] methods. +If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the [`ValueError`][value-error]. + +Next, the [`isdigit`][isdigit] method is used to see if all of the remaining characters in the input are digits. +If so, it uses the [`int()`][int-constructor] constructor to return the string as an integer. + +Next, the elements in the `OPS` dictionary are iterated. +If the key name is in the input, then the [`replace()`][replace] method is used to replace the name in the input with the dunder method value. +If none of the key names are found in the input, then a `ValueError` is returned for having an unknown operation. + +At this point the input question is [`split()`][split] into a list of its separate words, which is then iterated. + +The dunder methods can be called by using the [`__getattribute__`][getattribute] method for [`int`][int]. + +[dictionaries]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python +[int]: https://docs.python.org/3/library/stdtypes.html#typesnumeric +[dir]: https://docs.python.org/3/library/functions.html?#dir +[const]: https://realpython.com/python-constants/ +[removeprefix]: https://docs.python.org/3/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3/library/stdtypes.html#str.removesuffix +[strip]: https://docs.python.org/3/library/stdtypes.html#str.strip +[not]: https://docs.python.org/3/library/operator.html?#operator.__not__ +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[value-error]: https://docs.python.org/3/library/exceptions.html?#ValueError +[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit +[int-constructor]: https://docs.python.org/3/library/functions.html?#int +[replace]: https://docs.python.org/3/library/stdtypes.html?#str.replace +[split]: https://docs.python.org/3/library/stdtypes.html?#str.split + +[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ + From ae80f0de25607ec2b8c8390a0e136a0827067c78 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Mon, 28 Nov 2022 10:58:23 -0600 Subject: [PATCH 152/826] Update content.md --- .../dunder-getattribute/content.md | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index 70df27c450..7856b55e27 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -42,14 +42,19 @@ They are also called magic methods. Since only whole numbers are involved, the dunder methods are those for [`int`][int]. The supported methods for `int` can be found by using `print(dir(int))`. -The built-in [`dir`][dir] function returns a list of valid attributes for an object. + +```exercism/note +The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of valid attributes for an object. +``` Python doesn't _enforce_ having real constant values, but the `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. It indicates that the value is not intended to be changed. The input question to the `answer` function is cleaned using the [`removeprefix`][removeprefix], [`removesuffix`][removesuffix], and [`strip`][strip] methods. -If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the [`ValueError`][value-error]. +The method calls are [chained][method-chaining], so that the output from one call is the input for the next call. +If the input has no characters left, +it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the [`ValueError`][value-error] for having a syntax error. Next, the [`isdigit`][isdigit] method is used to see if all of the remaining characters in the input are digits. If so, it uses the [`int()`][int-constructor] constructor to return the string as an integer. @@ -58,18 +63,31 @@ Next, the elements in the `OPS` dictionary are iterated. If the key name is in the input, then the [`replace()`][replace] method is used to replace the name in the input with the dunder method value. If none of the key names are found in the input, then a `ValueError` is returned for having an unknown operation. -At this point the input question is [`split()`][split] into a list of its separate words, which is then iterated. +At this point the input question is [`split()`][split] into a list of its words, which is then iterated while its [`len()`][len] is greater than 1. -The dunder methods can be called by using the [`__getattribute__`][getattribute] method for [`int`][int]. +Within a [try][exception-handling], the list is [destructured][destructure] into `x, op, y, *tail`. +If `op` is not in the supported dunder methods, it raises `ValueError("syntax error")`. +If there are any other exceptions raised in the try, `except` raises `ValueError("syntax error")` + +Next, it converts `x` to an `int` and calls the [`__getattribute__`][getattribute] for its dunder method and calls it, +passing it `y` converted to an `int`. + +It sets the list to the result of the dunder method plus the remaining elements in `*tail`. + +```exercism/note +The `*` prefix in `*tail` [unpacks](https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/) the `tail` list back into its elements. +``` + +When the loop exhausts, the first element of the list is selected as the function return value. [dictionaries]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python [int]: https://docs.python.org/3/library/stdtypes.html#typesnumeric -[dir]: https://docs.python.org/3/library/functions.html?#dir [const]: https://realpython.com/python-constants/ [removeprefix]: https://docs.python.org/3/library/stdtypes.html#str.removeprefix [removesuffix]: https://docs.python.org/3/library/stdtypes.html#str.removesuffix [strip]: https://docs.python.org/3/library/stdtypes.html#str.strip +[method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining [not]: https://docs.python.org/3/library/operator.html?#operator.__not__ [falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ [value-error]: https://docs.python.org/3/library/exceptions.html?#ValueError @@ -77,6 +95,7 @@ The dunder methods can be called by using the [`__getattribute__`][getattribute] [int-constructor]: https://docs.python.org/3/library/functions.html?#int [replace]: https://docs.python.org/3/library/stdtypes.html?#str.replace [split]: https://docs.python.org/3/library/stdtypes.html?#str.split - +[len]: https://docs.python.org/3/library/functions.html?#len +[exception-handling]: https://docs.python.org/3/tutorial/errors.html#handling-exceptions +[destructure]: https://riptutorial.com/python/example/14981/destructuring-assignment [getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ - From 997a55552e26d1858d000b434b8b48f090ddf3ae Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 08:38:04 -0600 Subject: [PATCH 153/826] Update and rename snippet.md to snippet.txt --- .../.approaches/dunder-getattribute/{snippet.md => snippet.txt} | 2 -- 1 file changed, 2 deletions(-) rename exercises/practice/wordy/.approaches/dunder-getattribute/{snippet.md => snippet.txt} (94%) diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.md b/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.txt similarity index 94% rename from exercises/practice/wordy/.approaches/dunder-getattribute/snippet.md rename to exercises/practice/wordy/.approaches/dunder-getattribute/snippet.txt index c3aeda2fe9..d3cc3d1670 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.txt @@ -1,4 +1,3 @@ -```python while len(ret) > 1: try: x, op, y, *tail = ret @@ -7,4 +6,3 @@ while len(ret) > 1: except: raise ValueError("syntax error") return ret[0] -``` From 3bd743d72cc5d64f8fe46d59aef9c1290e1ecf39 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 3 Dec 2022 13:37:25 -0800 Subject: [PATCH 154/826] Apply suggestions from code review --- .../wordy/.approaches/dunder-getattribute/content.md | 6 +++--- exercises/practice/wordy/.approaches/introduction.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index 7856b55e27..ed156c6ed5 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -14,12 +14,12 @@ def answer(question): if not question: raise ValueError("syntax error") if question.isdigit(): return int(question) - foundOp = False + found_op = False for name, op in OPS.items(): if name in question: question = question.replace(name, op) - foundOp = True - if not foundOp: raise ValueError("unknown operation") + found_op = True + if not found_op: raise ValueError("unknown operation") ret = question.split() while len(ret) > 1: diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 32d6a92038..c8a5feb6f3 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -31,12 +31,12 @@ def answer(question): if not question: raise ValueError("syntax error") if question.isdigit(): return int(question) - foundOp = False + found_op = False for name, op in OPS.items(): if name in question: question = question.replace(name, op) - foundOp = True - if not foundOp: raise ValueError("unknown operation") + found_op = True + if not found_op: raise ValueError("unknown operation") ret = question.split() while len(ret) > 1: From 6182702de12443ed2a0ff375fa08f28b2f3ae3b5 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sat, 3 Dec 2022 22:51:18 +0100 Subject: [PATCH 155/826] Add link to concept --- .../practice/wordy/.approaches/dunder-getattribute/content.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index ed156c6ed5..f64bc6fac3 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -76,6 +76,7 @@ It sets the list to the result of the dunder method plus the remaining elements ```exercism/note The `*` prefix in `*tail` [unpacks](https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/) the `tail` list back into its elements. +This concept is also aparat of [unpacking-and-multiple-assignment](https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment) concept in the syllabus.. ``` When the loop exhausts, the first element of the list is selected as the function return value. From bae4589faffb51543d1b96050fc2568051a44a84 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 11:22:27 +0100 Subject: [PATCH 156/826] Added bottle_song --- config.json | 20 +++ .../bottle-song/.docs/instructions.md | 57 ++++++++ .../practice/bottle-song/.meta/config.json | 19 +++ .../practice/bottle-song/.meta/example.py | 34 +++++ .../practice/bottle-song/.meta/template.j2 | 23 +++ .../practice/bottle-song/.meta/tests.toml | 31 ++++ exercises/practice/bottle-song/bottle_song.py | 2 + .../practice/bottle-song/bottle_song_test.py | 136 ++++++++++++++++++ 8 files changed, 322 insertions(+) create mode 100644 exercises/practice/bottle-song/.docs/instructions.md create mode 100644 exercises/practice/bottle-song/.meta/config.json create mode 100644 exercises/practice/bottle-song/.meta/example.py create mode 100644 exercises/practice/bottle-song/.meta/template.j2 create mode 100644 exercises/practice/bottle-song/.meta/tests.toml create mode 100644 exercises/practice/bottle-song/bottle_song.py create mode 100644 exercises/practice/bottle-song/bottle_song_test.py diff --git a/config.json b/config.json index df8633bb8f..912e9b6e3e 100644 --- a/config.json +++ b/config.json @@ -2151,6 +2151,26 @@ "difficulty": 3, "status": "deprecated" }, + { + "slug": "bottle-song", + "name": "Bottle Song", + "uuid": "70bec74a-0677-40c9-b9c9-cbcc49a2eae4", + "practices": ["generators"], + "prerequisites": [ + "basics", + "conditionals", + "dicts", + "lists", + "list-methods", + "loops", + "numbers", + "strings", + "string-methods", + "tuples" + ], + "difficulty": 3, + "status": "deprecated" + }, { "slug": "trinary", "name": "Trinary", diff --git a/exercises/practice/bottle-song/.docs/instructions.md b/exercises/practice/bottle-song/.docs/instructions.md new file mode 100644 index 0000000000..febdfc8639 --- /dev/null +++ b/exercises/practice/bottle-song/.docs/instructions.md @@ -0,0 +1,57 @@ +# Instructions + +Recite the lyrics to that popular children's repetitive song: Ten Green Bottles. + +Note that not all verses are identical. + +```text +Ten green bottles hanging on the wall, +Ten green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be nine green bottles hanging on the wall. + +Nine green bottles hanging on the wall, +Nine green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be eight green bottles hanging on the wall. + +Eight green bottles hanging on the wall, +Eight green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be seven green bottles hanging on the wall. + +Seven green bottles hanging on the wall, +Seven green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be six green bottles hanging on the wall. + +Six green bottles hanging on the wall, +Six green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be five green bottles hanging on the wall. + +Five green bottles hanging on the wall, +Five green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be four green bottles hanging on the wall. + +Four green bottles hanging on the wall, +Four green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be three green bottles hanging on the wall. + +Three green bottles hanging on the wall, +Three green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be two green bottles hanging on the wall. + +Two green bottles hanging on the wall, +Two green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be one green bottle hanging on the wall. + +One green bottle hanging on the wall, +One green bottle hanging on the wall, +And if one green bottle should accidentally fall, +There'll be no green bottles hanging on the wall. +``` diff --git a/exercises/practice/bottle-song/.meta/config.json b/exercises/practice/bottle-song/.meta/config.json new file mode 100644 index 0000000000..7845207fe5 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/config.json @@ -0,0 +1,19 @@ +{ + "blurb": "Produce the lyrics to the popular children's repetitive song: Ten Green Bottles.", + "authors": ["meatball133", "BethanyG"], + "contributors": [ + ], + "files": { + "solution": [ + "bottle_song.py" + ], + "test": [ + "bottle_song_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Ten_Green_Bottles" +} diff --git a/exercises/practice/bottle-song/.meta/example.py b/exercises/practice/bottle-song/.meta/example.py new file mode 100644 index 0000000000..18e1167da3 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/example.py @@ -0,0 +1,34 @@ +NUMBERS = {10: "ten", 9: "nine", 8: "eight", 7: "seven", 6: "six", 5: "five", 4: "four", 3: "three", 2: "two", 1: "one", 0: "no"} + +def recite(start, take=1): + results = [] + for idx in range(start, start - take, -1): + results.extend(verse(idx)) + if idx > start - take + 1: + results.append('') + return results + + +def verse(number): + return [ + *main_verse(number), + "And if one green bottle should accidentally fall,", + last_verse(number) + ] + +def main_verse(number): + if number == 1: + return [ + f'One green bottle hanging on the wall,', + f'One green bottle hanging on the wall,', + ] + else: + return [ + f'{NUMBERS[number].capitalize()} green bottles hanging on the wall,', + f'{NUMBERS[number].capitalize()} green bottles hanging on the wall,',] + +def last_verse(number): + if number -1 == 1: + return f"There'll be one green bottle hanging on the wall." + else: + return f"There'll be {NUMBERS[number-1]} green bottles hanging on the wall." diff --git a/exercises/practice/bottle-song/.meta/template.j2 b/exercises/practice/bottle-song/.meta/template.j2 new file mode 100644 index 0000000000..37e45402da --- /dev/null +++ b/exercises/practice/bottle-song/.meta/template.j2 @@ -0,0 +1,23 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.header() }} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + {% for subcase in case["cases"] -%} + {% for subsubcase in subcase["cases"] -%} + def test_{{ subsubcase["description"] | to_snake }}(self): + {% set start = subsubcase["input"]["startBottles"] -%} + {% set take = subsubcase["input"]["takeDown"] -%} + expected = {{ subsubcase["expected"] }} + {%- if take == 1 %} + self.assertEqual({{ subsubcase["property"] }}(start={{ start}}), expected) + {% else %} + self.assertEqual({{ subsubcase["property"] }}(start={{ start}}, take={{ take }}), expected) + {% endif %} + + {% endfor %} + {% endfor %} + {% endfor %} + + +{{ macros.footer() }} diff --git a/exercises/practice/bottle-song/.meta/tests.toml b/exercises/practice/bottle-song/.meta/tests.toml new file mode 100644 index 0000000000..1f6e40a37c --- /dev/null +++ b/exercises/practice/bottle-song/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d4ccf8fc-01dc-48c0-a201-4fbeb30f2d03] +description = "verse -> single verse -> first generic verse" + +[0f0aded3-472a-4c64-b842-18d4f1f5f030] +description = "verse -> single verse -> last generic verse" + +[f61f3c97-131f-459e-b40a-7428f3ed99d9] +description = "verse -> single verse -> verse with 2 bottles" + +[05eadba9-5dbd-401e-a7e8-d17cc9baa8e0] +description = "verse -> single verse -> verse with 1 bottle" + +[a4a28170-83d6-4dc1-bd8b-319b6abb6a80] +description = "lyrics -> multiple verses -> first two verses" + +[3185d438-c5ac-4ce6-bcd3-02c9ff1ed8db] +description = "lyrics -> multiple verses -> last three verses" + +[28c1584a-0e51-4b65-9ae2-fbc0bf4bbb28] +description = "lyrics -> multiple verses -> all verses" diff --git a/exercises/practice/bottle-song/bottle_song.py b/exercises/practice/bottle-song/bottle_song.py new file mode 100644 index 0000000000..9a5e67fc8b --- /dev/null +++ b/exercises/practice/bottle-song/bottle_song.py @@ -0,0 +1,2 @@ +def recite(start, take=1): + pass diff --git a/exercises/practice/bottle-song/bottle_song_test.py b/exercises/practice/bottle-song/bottle_song_test.py new file mode 100644 index 0000000000..89877dc494 --- /dev/null +++ b/exercises/practice/bottle-song/bottle_song_test.py @@ -0,0 +1,136 @@ +import unittest + +from bottle_song import ( + recite, +) + +# Tests adapted from `problem-specifications//canonical-data.json` + + +class BottleSongTest(unittest.TestCase): + def test_first_generic_verse(self): + expected = [ + "Ten green bottles hanging on the wall,", + "Ten green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be nine green bottles hanging on the wall.", + ] + self.assertEqual(recite(start=10), expected) + + def test_last_generic_verse(self): + expected = [ + "Three green bottles hanging on the wall,", + "Three green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be two green bottles hanging on the wall.", + ] + self.assertEqual(recite(start=3), expected) + + def test_verse_with_2_bottles(self): + expected = [ + "Two green bottles hanging on the wall,", + "Two green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be one green bottle hanging on the wall.", + ] + self.assertEqual(recite(start=2), expected) + + def test_verse_with_1_bottle(self): + expected = [ + "One green bottle hanging on the wall,", + "One green bottle hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be no green bottles hanging on the wall.", + ] + self.assertEqual(recite(start=1), expected) + + def test_first_two_verses(self): + expected = [ + "Ten green bottles hanging on the wall,", + "Ten green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be nine green bottles hanging on the wall.", + "", + "Nine green bottles hanging on the wall,", + "Nine green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be eight green bottles hanging on the wall.", + ] + self.assertEqual(recite(start=10, take=2), expected) + + def test_last_three_verses(self): + expected = [ + "Three green bottles hanging on the wall,", + "Three green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be two green bottles hanging on the wall.", + "", + "Two green bottles hanging on the wall,", + "Two green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be one green bottle hanging on the wall.", + "", + "One green bottle hanging on the wall,", + "One green bottle hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be no green bottles hanging on the wall.", + ] + self.assertEqual(recite(start=3, take=3), expected) + + def test_all_verses(self): + expected = [ + "Ten green bottles hanging on the wall,", + "Ten green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be nine green bottles hanging on the wall.", + "", + "Nine green bottles hanging on the wall,", + "Nine green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be eight green bottles hanging on the wall.", + "", + "Eight green bottles hanging on the wall,", + "Eight green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be seven green bottles hanging on the wall.", + "", + "Seven green bottles hanging on the wall,", + "Seven green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be six green bottles hanging on the wall.", + "", + "Six green bottles hanging on the wall,", + "Six green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be five green bottles hanging on the wall.", + "", + "Five green bottles hanging on the wall,", + "Five green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be four green bottles hanging on the wall.", + "", + "Four green bottles hanging on the wall,", + "Four green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be three green bottles hanging on the wall.", + "", + "Three green bottles hanging on the wall,", + "Three green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be two green bottles hanging on the wall.", + "", + "Two green bottles hanging on the wall,", + "Two green bottles hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be one green bottle hanging on the wall.", + "", + "One green bottle hanging on the wall,", + "One green bottle hanging on the wall,", + "And if one green bottle should accidentally fall,", + "There'll be no green bottles hanging on the wall.", + ] + self.assertEqual(recite(start=10, take=10), expected) + + +if __name__ == "__main__": + unittest.main() From 1579133bc28270d8d41dacc356a072a826247889 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 11:24:55 +0100 Subject: [PATCH 157/826] fxi config --- config.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config.json b/config.json index 912e9b6e3e..2c0e439bb7 100644 --- a/config.json +++ b/config.json @@ -997,9 +997,9 @@ "difficulty": 3 }, { - "slug": "beer-song", - "name": "Beer Song", - "uuid": "b7984882-65df-4993-a878-7872c776592a", + "slug": "bottle-song", + "name": "Bottle Song", + "uuid": "70bec74a-0677-40c9-b9c9-cbcc49a2eae4", "practices": ["generators"], "prerequisites": [ "basics", @@ -2152,9 +2152,9 @@ "status": "deprecated" }, { - "slug": "bottle-song", - "name": "Bottle Song", - "uuid": "70bec74a-0677-40c9-b9c9-cbcc49a2eae4", + "slug": "beer-song", + "name": "Beer Song", + "uuid": "b7984882-65df-4993-a878-7872c776592a", "practices": ["generators"], "prerequisites": [ "basics", From 746abfc27043f574f40deb888977fa8bbc86f2d2 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 11:29:54 +0100 Subject: [PATCH 158/826] fixes --- config.json | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/config.json b/config.json index 2c0e439bb7..0df05d3736 100644 --- a/config.json +++ b/config.json @@ -2155,19 +2155,8 @@ "slug": "beer-song", "name": "Beer Song", "uuid": "b7984882-65df-4993-a878-7872c776592a", - "practices": ["generators"], - "prerequisites": [ - "basics", - "conditionals", - "dicts", - "lists", - "list-methods", - "loops", - "numbers", - "strings", - "string-methods", - "tuples" - ], + "practices": [], + "prerequisites": [], "difficulty": 3, "status": "deprecated" }, From 31e2c88a28a53834510ce31856a65f970292a51a Mon Sep 17 00:00:00 2001 From: Meatball Date: Fri, 2 Dec 2022 23:10:21 +0100 Subject: [PATCH 159/826] started work --- config.json | 19 ++++++++------- .../practice/proverb/.docs/instructions.md | 6 +++-- exercises/practice/proverb/.meta/config.json | 2 +- exercises/practice/proverb/.meta/template.j2 | 24 +++++++++++++++++++ 4 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 exercises/practice/proverb/.meta/template.j2 diff --git a/config.json b/config.json index 0df05d3736..2d2ab01976 100644 --- a/config.json +++ b/config.json @@ -653,6 +653,16 @@ ], "difficulty": 2 }, + { + "slug": "proverb", + "name": "Proverb", + "uuid": "9fd94229-f974-45bb-97ea-8bfe484f6eb3", + "practices": ["unpacking-and-multiple-assignment"], + "prerequisites": ["dicts", + "unpacking-and-multiple-assignment"], + "difficulty": 2, + "status": "wip" + }, { "slug": "yacht", "name": "Yacht", @@ -2061,15 +2071,6 @@ "difficulty": 2, "status": "deprecated" }, - { - "slug": "proverb", - "name": "Proverb", - "uuid": "9fd94229-f974-45bb-97ea-8bfe484f6eb3", - "practices": [], - "prerequisites": [], - "difficulty": 2, - "status": "deprecated" - }, { "slug": "nucleotide-count", "name": "Nucleotide Count", diff --git a/exercises/practice/proverb/.docs/instructions.md b/exercises/practice/proverb/.docs/instructions.md index cf3b4c8b28..f6fb859325 100644 --- a/exercises/practice/proverb/.docs/instructions.md +++ b/exercises/practice/proverb/.docs/instructions.md @@ -2,7 +2,8 @@ For want of a horseshoe nail, a kingdom was lost, or so the saying goes. -Given a list of inputs, generate the relevant proverb. For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: +Given a list of inputs, generate the relevant proverb. +For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: ```text For want of a nail the shoe was lost. @@ -14,4 +15,5 @@ For want of a battle the kingdom was lost. And all for the want of a nail. ``` -Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content. No line of the output text should be a static, unchanging string; all should vary according to the input given. +Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content. +No line of the output text should be a static, unchanging string; all should vary according to the input given. diff --git a/exercises/practice/proverb/.meta/config.json b/exercises/practice/proverb/.meta/config.json index 690feff3c8..6bebac6f24 100644 --- a/exercises/practice/proverb/.meta/config.json +++ b/exercises/practice/proverb/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/For_Want_of_a_Nail" + "source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail" } diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 new file mode 100644 index 0000000000..e90fe71307 --- /dev/null +++ b/exercises/practice/proverb/.meta/template.j2 @@ -0,0 +1,24 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.header() }} +{{ "# PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.**" }} +{{ "# A new line in a result list below **does not** always equal a new list element." }} +{{ "# Check comma placement carefully!" }} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {# All test cases in this exercise are nested, so use two for loops -#} + {% for supercase in cases -%}{% for case in supercase["cases"] -%} + {% set input = case["input"] -%} + def test_{{ case["description"] | to_snake }}(self): + {% if supercase["description"] == "verse" -%} + expected = [ + {{ macros.linebreak(case["expected"][0]) }} + ] + {% else -%} + expected = [recite(n, n)[0] for n in range( + {{- input["startVerse"] }}, + {{- input["endVerse"] + 1 }})] + {% endif -%} + self.assertEqual({{ case["property"]}}( + {{- input["startVerse"] }}, + {{- input["endVerse"] }}), expected) + {% endfor %}{% endfor %} From a1a0350b2d3dbfa603258da21d7f3525b0f25e5d Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 16:21:18 +0100 Subject: [PATCH 160/826] Added extra tests --- .../proverb/.meta/additional_tests.json | 36 +++++ exercises/practice/proverb/.meta/config.json | 4 +- exercises/practice/proverb/.meta/template.j2 | 46 +++--- exercises/practice/proverb/proverb_test.py | 139 +++++++++++++----- 4 files changed, 170 insertions(+), 55 deletions(-) create mode 100644 exercises/practice/proverb/.meta/additional_tests.json diff --git a/exercises/practice/proverb/.meta/additional_tests.json b/exercises/practice/proverb/.meta/additional_tests.json new file mode 100644 index 0000000000..8a6a670577 --- /dev/null +++ b/exercises/practice/proverb/.meta/additional_tests.json @@ -0,0 +1,36 @@ +{ + "cases": [ + { + "description": "sentence without lower bound", + "property": "proverb", + "input": { + "strings": ["nail"], + "extra": {"qualifier": "horseshoe"} + }, + "expected": ["And all for the want of a horseshoe nail."] + }, + { + "description": "sentence without upper bound", + "property": "proverb", + "input": { + "strings": [ + "nail", + "shoe", + "horse", + "rider", + "message", + "battle", + "kingdom" + ], + "extra": {"qualifier": "horseshoe"} + }, + "expected": ["For want of a nail the shoe was lost.", + "For want of a shoe the horse was lost.", + "For want of a horse the rider was lost.", + "For want of a rider the message was lost.", + "For want of a message the battle was lost.", + "For want of a battle the kingdom was lost.", + "And all for the want of a horseshoe nail."] + } + ] +} diff --git a/exercises/practice/proverb/.meta/config.json b/exercises/practice/proverb/.meta/config.json index 6bebac6f24..d413b0455b 100644 --- a/exercises/practice/proverb/.meta/config.json +++ b/exercises/practice/proverb/.meta/config.json @@ -1,7 +1,9 @@ { "blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.", "authors": [ - "betegelse" + "betegelse", + "meatball133", + "BethanyG" ], "contributors": [ "behrtam", diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 index e90fe71307..91def8fee2 100644 --- a/exercises/practice/proverb/.meta/template.j2 +++ b/exercises/practice/proverb/.meta/template.j2 @@ -1,24 +1,36 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.header(imports=["proverb"]) }} {{ "# PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.**" }} {{ "# A new line in a result list below **does not** always equal a new list element." }} {{ "# Check comma placement carefully!" }} class {{ exercise | camel_case }}Test(unittest.TestCase): {# All test cases in this exercise are nested, so use two for loops -#} - {% for supercase in cases -%}{% for case in supercase["cases"] -%} - {% set input = case["input"] -%} - def test_{{ case["description"] | to_snake }}(self): - {% if supercase["description"] == "verse" -%} - expected = [ - {{ macros.linebreak(case["expected"][0]) }} - ] - {% else -%} - expected = [recite(n, n)[0] for n in range( - {{- input["startVerse"] }}, - {{- input["endVerse"] + 1 }})] - {% endif -%} - self.assertEqual({{ case["property"]}}( - {{- input["startVerse"] }}, - {{- input["endVerse"] }}), expected) - {% endfor %}{% endfor %} + {%- macro test_case(case) -%} + {% set input = case["input"] -%} + def test_{{ case["description"] | to_snake }}(self): + self.assertEqual(proverb( + {% if input["strings"]|length >= 1 %} + {% for item in input["strings"] %} + "{{ item }}", + {% endfor %} + {% else %} + "" + {% endif %} + {% if input["extra"] %} + qualifier="{{ input["extra"]["qualifier"] }}" + {% endif %} + ), + {{ case["expected"] }} + ) + {%- endmacro -%} + + {% for case in cases -%} + {{ test_case(case) }} + {% endfor %} + + # Track-specific tests + {% for cases in additional_cases -%} + {{ test_case(cases) }} + {% endfor %} + diff --git a/exercises/practice/proverb/proverb_test.py b/exercises/practice/proverb/proverb_test.py index c57c71b905..511cde36c1 100644 --- a/exercises/practice/proverb/proverb_test.py +++ b/exercises/practice/proverb/proverb_test.py @@ -1,52 +1,117 @@ import unittest -from proverb import proverb +from proverb import ( + proverb, +) +# Tests adapted from `problem-specifications//canonical-data.json` +# PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.** +# A new line in a result list below **does not** always equal a new list element. +# Check comma placement carefully! -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 class ProverbTest(unittest.TestCase): def test_zero_pieces(self): - self.assertEqual(proverb([]), "") + self.assertEqual(proverb(""), []) def test_one_piece(self): - inputs = ["nail"] - expected = "And all for the want of a nail." - self.assertEqual(proverb(inputs), expected) + self.assertEqual( + proverb( + "nail", + ), + ["And all for the want of a nail."], + ) def test_two_pieces(self): - inputs = ["nail", "shoe"] - expected = "\n".join(["For want of a nail the shoe was lost.", - "And all for the want of a nail."]) - self.assertEqual(proverb(inputs), expected) + self.assertEqual( + proverb( + "nail", + "shoe", + ), + [ + "For want of a nail the shoe was lost.", + "And all for the want of a nail.", + ], + ) def test_three_pieces(self): - inputs = ["nail", "shoe", "horse"] - expected = "\n".join(["For want of a nail the shoe was lost.", - "For want of a shoe the horse was lost.", - "And all for the want of a nail."]) - self.assertEqual(proverb(inputs), expected) + self.assertEqual( + proverb( + "nail", + "shoe", + "horse", + ), + [ + "For want of a nail the shoe was lost.", + "For want of a shoe the horse was lost.", + "And all for the want of a nail.", + ], + ) def test_full_proverb(self): - inputs = ["nail", "shoe", "horse", "rider", - "message", "battle", "kingdom"] - expected = "\n".join(["For want of a nail the shoe was lost.", - "For want of a shoe the horse was lost.", - "For want of a horse the rider was lost.", - "For want of a rider the message was lost.", - "For want of a message the battle was lost.", - "For want of a battle the kingdom was lost.", - "And all for the want of a nail."]) - self.assertEqual(proverb(inputs), expected) - - def test_four_pieces_modernised(self): - inputs = ["pin", "gun", "soldier", "battle"] - expected = "\n".join(["For want of a pin the gun was lost.", - "For want of a gun the soldier was lost.", - "For want of a soldier the battle was lost.", - "And all for the want of a pin."]) - self.assertEqual(proverb(inputs), expected) - - -if __name__ == '__main__': - unittest.main() + self.assertEqual( + proverb( + "nail", + "shoe", + "horse", + "rider", + "message", + "battle", + "kingdom", + ), + [ + "For want of a nail the shoe was lost.", + "For want of a shoe the horse was lost.", + "For want of a horse the rider was lost.", + "For want of a rider the message was lost.", + "For want of a message the battle was lost.", + "For want of a battle the kingdom was lost.", + "And all for the want of a nail.", + ], + ) + + def test_four_pieces_modernized(self): + self.assertEqual( + proverb( + "pin", + "gun", + "soldier", + "battle", + ), + [ + "For want of a pin the gun was lost.", + "For want of a gun the soldier was lost.", + "For want of a soldier the battle was lost.", + "And all for the want of a pin.", + ], + ) + + # Track-specific tests + def test_sentence_without_lower_bound(self): + self.assertEqual( + proverb("nail", qualifier="horseshoe"), + ["And all for the want of a horseshoe nail."], + ) + + def test_sentence_without_upper_bound(self): + self.assertEqual( + proverb( + "nail", + "shoe", + "horse", + "rider", + "message", + "battle", + "kingdom", + qualifier="horseshoe", + ), + [ + "For want of a nail the shoe was lost.", + "For want of a shoe the horse was lost.", + "For want of a horse the rider was lost.", + "For want of a rider the message was lost.", + "For want of a message the battle was lost.", + "For want of a battle the kingdom was lost.", + "And all for the want of a horseshoe nail.", + ], + ) From 022cf760cc0a0786caffba553575993c459515d4 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 17:28:16 +0100 Subject: [PATCH 161/826] Added example --- exercises/practice/proverb/.meta/example.py | 12 ++++++++---- exercises/practice/proverb/.meta/template.j2 | 2 +- exercises/practice/proverb/proverb_test.py | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/exercises/practice/proverb/.meta/example.py b/exercises/practice/proverb/.meta/example.py index ec434ec7ce..2bb6be6eef 100644 --- a/exercises/practice/proverb/.meta/example.py +++ b/exercises/practice/proverb/.meta/example.py @@ -1,7 +1,11 @@ -def proverb(rhyme_items): +def proverb(*rhyme_items, qualifier=None): + print(rhyme_items) if not rhyme_items: - return '' + return [] phrases = [f'For want of a {element_1} the {element_2} was lost.' for element_1, element_2 in zip(rhyme_items, rhyme_items[1:])] - phrases.append(f'And all for the want of a {rhyme_items[0]}.') - return '\n'.join(phrases) + if qualifier: + phrases.append(f'And all for the want of a {qualifier} {rhyme_items[0]}.') + else: + phrases.append(f'And all for the want of a {rhyme_items[0]}.') + return phrases diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 index 91def8fee2..b856ec87ae 100644 --- a/exercises/practice/proverb/.meta/template.j2 +++ b/exercises/practice/proverb/.meta/template.j2 @@ -15,7 +15,7 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): "{{ item }}", {% endfor %} {% else %} - "" + {% endif %} {% if input["extra"] %} qualifier="{{ input["extra"]["qualifier"] }}" diff --git a/exercises/practice/proverb/proverb_test.py b/exercises/practice/proverb/proverb_test.py index 511cde36c1..00abe2e05f 100644 --- a/exercises/practice/proverb/proverb_test.py +++ b/exercises/practice/proverb/proverb_test.py @@ -12,7 +12,7 @@ class ProverbTest(unittest.TestCase): def test_zero_pieces(self): - self.assertEqual(proverb(""), []) + self.assertEqual(proverb(), []) def test_one_piece(self): self.assertEqual( From eb633aefb399117aea83fd3d2970e674ccf7d09e Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 21:37:27 +0100 Subject: [PATCH 162/826] Updated jinja file --- exercises/practice/proverb/.meta/example.py | 2 +- exercises/practice/proverb/.meta/template.j2 | 21 +++----- exercises/practice/proverb/proverb.py | 2 +- exercises/practice/proverb/proverb_test.py | 57 ++++++-------------- 4 files changed, 27 insertions(+), 55 deletions(-) diff --git a/exercises/practice/proverb/.meta/example.py b/exercises/practice/proverb/.meta/example.py index 2bb6be6eef..8e6f30f551 100644 --- a/exercises/practice/proverb/.meta/example.py +++ b/exercises/practice/proverb/.meta/example.py @@ -1,4 +1,4 @@ -def proverb(*rhyme_items, qualifier=None): +def proverb(*rhyme_items, qualifier): print(rhyme_items) if not rhyme_items: return [] diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 index b856ec87ae..cf50a26190 100644 --- a/exercises/practice/proverb/.meta/template.j2 +++ b/exercises/practice/proverb/.meta/template.j2 @@ -9,20 +9,14 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {%- macro test_case(case) -%} {% set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): - self.assertEqual(proverb( - {% if input["strings"]|length >= 1 %} - {% for item in input["strings"] %} - "{{ item }}", - {% endfor %} - {% else %} - - {% endif %} - {% if input["extra"] %} + input_data = {{ input["strings"] }} + self.assertEqual(proverb(*input_data, + {%- if input["extra"] -%} qualifier="{{ input["extra"]["qualifier"] }}" - {% endif %} - ), - {{ case["expected"] }} - ) + {%- else -%} + qualifier=None + {%- endif -%} + ),{{ case["expected"] }}) {%- endmacro -%} {% for case in cases -%} @@ -30,6 +24,7 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} # Track-specific tests + {% for cases in additional_cases -%} {{ test_case(cases) }} {% endfor %} diff --git a/exercises/practice/proverb/proverb.py b/exercises/practice/proverb/proverb.py index aa9c1fa304..d1be410c11 100644 --- a/exercises/practice/proverb/proverb.py +++ b/exercises/practice/proverb/proverb.py @@ -1,2 +1,2 @@ -def proverb(rhyme_items): +def proverb(): pass diff --git a/exercises/practice/proverb/proverb_test.py b/exercises/practice/proverb/proverb_test.py index 00abe2e05f..c3a59d42b6 100644 --- a/exercises/practice/proverb/proverb_test.py +++ b/exercises/practice/proverb/proverb_test.py @@ -12,22 +12,19 @@ class ProverbTest(unittest.TestCase): def test_zero_pieces(self): - self.assertEqual(proverb(), []) + input_data = [] + self.assertEqual(proverb(*input_data, qualifier=None), []) def test_one_piece(self): + input_data = ["nail"] self.assertEqual( - proverb( - "nail", - ), - ["And all for the want of a nail."], + proverb(*input_data, qualifier=None), ["And all for the want of a nail."] ) def test_two_pieces(self): + input_data = ["nail", "shoe"] self.assertEqual( - proverb( - "nail", - "shoe", - ), + proverb(*input_data, qualifier=None), [ "For want of a nail the shoe was lost.", "And all for the want of a nail.", @@ -35,12 +32,9 @@ def test_two_pieces(self): ) def test_three_pieces(self): + input_data = ["nail", "shoe", "horse"] self.assertEqual( - proverb( - "nail", - "shoe", - "horse", - ), + proverb(*input_data, qualifier=None), [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", @@ -49,16 +43,9 @@ def test_three_pieces(self): ) def test_full_proverb(self): + input_data = ["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"] self.assertEqual( - proverb( - "nail", - "shoe", - "horse", - "rider", - "message", - "battle", - "kingdom", - ), + proverb(*input_data, qualifier=None), [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", @@ -71,13 +58,9 @@ def test_full_proverb(self): ) def test_four_pieces_modernized(self): + input_data = ["pin", "gun", "soldier", "battle"] self.assertEqual( - proverb( - "pin", - "gun", - "soldier", - "battle", - ), + proverb(*input_data, qualifier=None), [ "For want of a pin the gun was lost.", "For want of a gun the soldier was lost.", @@ -87,24 +70,18 @@ def test_four_pieces_modernized(self): ) # Track-specific tests + def test_sentence_without_lower_bound(self): + input_data = ["nail"] self.assertEqual( - proverb("nail", qualifier="horseshoe"), + proverb(*input_data, qualifier="horseshoe"), ["And all for the want of a horseshoe nail."], ) def test_sentence_without_upper_bound(self): + input_data = ["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"] self.assertEqual( - proverb( - "nail", - "shoe", - "horse", - "rider", - "message", - "battle", - "kingdom", - qualifier="horseshoe", - ), + proverb(*input_data, qualifier="horseshoe"), [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", From 3490eda71c943c294105ab8f08841e212f1f0764 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 21:41:43 +0100 Subject: [PATCH 163/826] fix --- exercises/practice/proverb/.meta/template.j2 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 index cf50a26190..3ef67fe21d 100644 --- a/exercises/practice/proverb/.meta/template.j2 +++ b/exercises/practice/proverb/.meta/template.j2 @@ -1,11 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} {{ macros.header(imports=["proverb"]) }} -{{ "# PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.**" }} -{{ "# A new line in a result list below **does not** always equal a new list element." }} -{{ "# Check comma placement carefully!" }} class {{ exercise | camel_case }}Test(unittest.TestCase): - {# All test cases in this exercise are nested, so use two for loops -#} {%- macro test_case(case) -%} {% set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): From f6ecae281c754ca3c1ea16e01461f24a49739f2e Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 21:44:27 +0100 Subject: [PATCH 164/826] reversed --- exercises/practice/proverb/.meta/template.j2 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 index 3ef67fe21d..cf50a26190 100644 --- a/exercises/practice/proverb/.meta/template.j2 +++ b/exercises/practice/proverb/.meta/template.j2 @@ -1,7 +1,11 @@ {%- import "generator_macros.j2" as macros with context -%} {{ macros.header(imports=["proverb"]) }} +{{ "# PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.**" }} +{{ "# A new line in a result list below **does not** always equal a new list element." }} +{{ "# Check comma placement carefully!" }} class {{ exercise | camel_case }}Test(unittest.TestCase): + {# All test cases in this exercise are nested, so use two for loops -#} {%- macro test_case(case) -%} {% set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): From 509342fd8a68b40dbd88d43a446dc3201b8cac4a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 4 Dec 2022 14:05:58 -0800 Subject: [PATCH 165/826] Create instructions.append.md Added qualifier explanation and link to unpanking-and-multiple-assignment. --- exercises/practice/proverb/.docs/instructions.append.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 exercises/practice/proverb/.docs/instructions.append.md diff --git a/exercises/practice/proverb/.docs/instructions.append.md b/exercises/practice/proverb/.docs/instructions.append.md new file mode 100644 index 0000000000..c7797d0d69 --- /dev/null +++ b/exercises/practice/proverb/.docs/instructions.append.md @@ -0,0 +1,9 @@ +# Instructions append + +In [concept:python/unpacking-and-multiple-assignment](https://github.com/exercism/python/tree/main/concepts/unpacking-and-multiple-assignment), you learned multiple techniques for working with `lists`/`tuples` of arbitrary length as well as function arguments of arbitrary length. +This exercise would be a great place to practice those techniques. + +## How this exercise is implemented for Python + +The test cases for this track add an additional keyword argument called `qualifier`. +You should use this keyword arguments value to modify the final verse of your Proverb. From 9e8621a47fab125af1a658b8ae37f51ce3ddc11d Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 4 Dec 2022 23:15:16 +0100 Subject: [PATCH 166/826] fix --- config.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config.json b/config.json index 2d2ab01976..b2a3c8ebc3 100644 --- a/config.json +++ b/config.json @@ -660,8 +660,7 @@ "practices": ["unpacking-and-multiple-assignment"], "prerequisites": ["dicts", "unpacking-and-multiple-assignment"], - "difficulty": 2, - "status": "wip" + "difficulty": 2 }, { "slug": "yacht", From 689b2cf3d2e8164f86244929829fb40b52a3486b Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 4 Dec 2022 23:40:23 +0100 Subject: [PATCH 167/826] updated test case --- exercises/practice/proverb/.meta/additional_tests.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/proverb/.meta/additional_tests.json b/exercises/practice/proverb/.meta/additional_tests.json index 8a6a670577..0d1cac001f 100644 --- a/exercises/practice/proverb/.meta/additional_tests.json +++ b/exercises/practice/proverb/.meta/additional_tests.json @@ -1,7 +1,7 @@ { "cases": [ { - "description": "sentence without lower bound", + "description": "an optional qualifier can be added", "property": "proverb", "input": { "strings": ["nail"], @@ -10,7 +10,7 @@ "expected": ["And all for the want of a horseshoe nail."] }, { - "description": "sentence without upper bound", + "description": "an optional qualifier in the final consequences", "property": "proverb", "input": { "strings": [ From 1fa29ba6cc74ff3cefe6da9e8fe7003a632e7ab6 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sun, 4 Dec 2022 23:48:49 +0100 Subject: [PATCH 168/826] fix --- exercises/practice/proverb/proverb_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/proverb/proverb_test.py b/exercises/practice/proverb/proverb_test.py index c3a59d42b6..69905dadf4 100644 --- a/exercises/practice/proverb/proverb_test.py +++ b/exercises/practice/proverb/proverb_test.py @@ -71,14 +71,14 @@ def test_four_pieces_modernized(self): # Track-specific tests - def test_sentence_without_lower_bound(self): + def test_an_optional_qualifier_can_be_added(self): input_data = ["nail"] self.assertEqual( proverb(*input_data, qualifier="horseshoe"), ["And all for the want of a horseshoe nail."], ) - def test_sentence_without_upper_bound(self): + def test_an_optional_qualifier_in_the_final_consequences(self): input_data = ["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"] self.assertEqual( proverb(*input_data, qualifier="horseshoe"), From 5dacb7aba9399a7ac0ad774863137c9c805d0cbe Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sun, 4 Dec 2022 12:40:09 +0100 Subject: [PATCH 169/826] Reformat exercise config files This runs the `bin/configlet fmt --update` command to reformat the exercise configs. --- .../concept/black-jack/.meta/config.json | 32 ++++++++++++++----- .../concept/card-games/.meta/config.json | 29 ++++++++++++----- .../concept/cater-waiter/.meta/config.json | 31 ++++++++++++------ .../.meta/config.json | 28 +++++++++++----- .../currency-exchange/.meta/config.json | 31 +++++++++++++----- .../ellens-alien-game/.meta/config.json | 29 ++++++++++++----- .../.meta/config.json | 31 ++++++++++++------ .../guidos-gorgeous-lasagna/.meta/config.json | 25 +++++++++++---- .../inventory-management/.meta/config.json | 29 ++++++++++++----- .../little-sisters-essay/.meta/config.json | 27 +++++++++++----- .../little-sisters-vocab/.meta/config.json | 23 +++++++++---- .../locomotive-engineer/.meta/config.json | 29 ++++++++++++----- .../concept/log-levels/.meta/config.json | 28 +++++++++++----- .../making-the-grade/.meta/config.json | 27 +++++++++++----- .../meltdown-mitigation/.meta/config.json | 27 +++++++++++----- .../concept/plane-tickets/.meta/config.json | 26 ++++++++++----- .../concept/pretty-leaflet/.meta/config.json | 27 +++++++++++----- .../restaurant-rozalynn/.meta/config.json | 23 +++++++++---- .../tisbury-treasure-hunt/.meta/config.json | 22 +++++++++---- exercises/practice/acronym/.meta/config.json | 2 +- .../practice/affine-cipher/.meta/config.json | 2 +- .../practice/all-your-base/.meta/config.json | 4 +-- .../practice/allergies/.meta/config.json | 2 +- .../practice/alphametics/.meta/config.json | 4 +-- exercises/practice/anagram/.meta/config.json | 2 +- .../armstrong-numbers/.meta/config.json | 2 +- .../practice/atbash-cipher/.meta/config.json | 2 +- .../practice/bank-account/.meta/config.json | 4 +-- .../practice/beer-song/.meta/config.json | 2 +- .../binary-search-tree/.meta/config.json | 2 +- .../practice/binary-search/.meta/config.json | 2 +- exercises/practice/bob/.meta/config.json | 2 +- .../practice/book-store/.meta/config.json | 2 +- exercises/practice/bowling/.meta/config.json | 2 +- exercises/practice/change/.meta/config.json | 2 +- .../circular-buffer/.meta/config.json | 2 +- exercises/practice/clock/.meta/config.json | 2 +- .../collatz-conjecture/.meta/config.json | 2 +- .../complex-numbers/.meta/config.json | 2 +- exercises/practice/connect/.meta/config.json | 4 +-- .../practice/crypto-square/.meta/config.json | 2 +- .../practice/custom-set/.meta/config.json | 4 +-- exercises/practice/darts/.meta/config.json | 2 +- exercises/practice/diamond/.meta/config.json | 2 +- .../difference-of-squares/.meta/config.json | 2 +- .../practice/diffie-hellman/.meta/config.json | 2 +- .../practice/dnd-character/.meta/config.json | 2 +- exercises/practice/dominoes/.meta/config.json | 4 +-- exercises/practice/dot-dsl/.meta/config.json | 2 +- exercises/practice/etl/.meta/config.json | 2 +- .../practice/flatten-array/.meta/config.json | 2 +- .../practice/food-chain/.meta/config.json | 2 +- exercises/practice/forth/.meta/config.json | 4 +-- .../practice/gigasecond/.meta/config.json | 2 +- .../practice/go-counting/.meta/config.json | 4 +-- .../practice/grade-school/.meta/config.json | 2 +- exercises/practice/grains/.meta/config.json | 2 +- exercises/practice/grep/.meta/config.json | 2 +- exercises/practice/hamming/.meta/config.json | 2 +- exercises/practice/hangman/.meta/config.json | 4 +-- .../practice/hello-world/.meta/config.json | 2 +- .../practice/high-scores/.meta/config.json | 2 +- exercises/practice/house/.meta/config.json | 2 +- .../practice/isbn-verifier/.meta/config.json | 2 +- exercises/practice/isogram/.meta/config.json | 2 +- .../kindergarten-garden/.meta/config.json | 2 +- exercises/practice/knapsack/.meta/config.json | 2 +- .../largest-series-product/.meta/config.json | 2 +- exercises/practice/leap/.meta/config.json | 2 +- exercises/practice/ledger/.meta/config.json | 4 +-- .../practice/linked-list/.meta/config.json | 2 +- exercises/practice/list-ops/.meta/config.json | 4 +-- exercises/practice/luhn/.meta/config.json | 2 +- exercises/practice/markdown/.meta/config.json | 4 +-- .../matching-brackets/.meta/config.json | 2 +- exercises/practice/matrix/.meta/config.json | 2 +- exercises/practice/meetup/.meta/config.json | 2 +- .../practice/minesweeper/.meta/config.json | 4 +-- .../practice/nth-prime/.meta/config.json | 2 +- .../practice/ocr-numbers/.meta/config.json | 2 +- exercises/practice/paasio/.meta/config.json | 2 +- .../palindrome-products/.meta/config.json | 2 +- exercises/practice/pangram/.meta/config.json | 2 +- .../perfect-numbers/.meta/config.json | 2 +- .../practice/phone-number/.meta/config.json | 2 +- .../practice/pig-latin/.meta/config.json | 2 +- exercises/practice/poker/.meta/config.json | 2 +- exercises/practice/pov/.meta/config.json | 2 +- .../practice/prime-factors/.meta/config.json | 2 +- .../protein-translation/.meta/config.json | 2 +- .../pythagorean-triplet/.meta/config.json | 2 +- .../practice/queen-attack/.meta/config.json | 2 +- .../rail-fence-cipher/.meta/config.json | 2 +- .../practice/raindrops/.meta/config.json | 2 +- .../rational-numbers/.meta/config.json | 2 +- exercises/practice/react/.meta/config.json | 4 +-- .../practice/rectangles/.meta/config.json | 4 +-- .../resistor-color-duo/.meta/config.json | 2 +- .../resistor-color-trio/.meta/config.json | 4 +-- .../practice/resistor-color/.meta/config.json | 2 +- exercises/practice/rest-api/.meta/config.json | 4 +-- .../practice/reverse-string/.meta/config.json | 2 +- .../rna-transcription/.meta/config.json | 2 +- .../practice/robot-name/.meta/config.json | 2 +- .../robot-simulator/.meta/config.json | 2 +- .../practice/roman-numerals/.meta/config.json | 2 +- .../rotational-cipher/.meta/config.json | 2 +- .../run-length-encoding/.meta/config.json | 2 +- .../practice/saddle-points/.meta/config.json | 2 +- .../practice/satellite/.meta/config.json | 4 +-- exercises/practice/say/.meta/config.json | 2 +- .../scale-generator/.meta/config.json | 4 +-- .../practice/scrabble-score/.meta/config.json | 2 +- .../secret-handshake/.meta/config.json | 2 +- exercises/practice/series/.meta/config.json | 2 +- .../practice/sgf-parsing/.meta/config.json | 4 +-- exercises/practice/sieve/.meta/config.json | 2 +- .../practice/simple-cipher/.meta/config.json | 2 +- .../simple-linked-list/.meta/config.json | 2 +- .../practice/space-age/.meta/config.json | 2 +- .../practice/spiral-matrix/.meta/config.json | 2 +- exercises/practice/sublist/.meta/config.json | 4 +-- .../sum-of-multiples/.meta/config.json | 2 +- .../practice/tournament/.meta/config.json | 4 +-- .../practice/transpose/.meta/config.json | 2 +- .../practice/tree-building/.meta/config.json | 4 +-- exercises/practice/triangle/.meta/config.json | 2 +- .../practice/twelve-days/.meta/config.json | 2 +- .../practice/two-bucket/.meta/config.json | 2 +- exercises/practice/two-fer/.meta/config.json | 2 +- .../.meta/config.json | 2 +- .../practice/word-count/.meta/config.json | 2 +- .../practice/word-search/.meta/config.json | 4 +-- exercises/practice/wordy/.meta/config.json | 2 +- exercises/practice/yacht/.meta/config.json | 2 +- .../practice/zebra-puzzle/.meta/config.json | 2 +- exercises/practice/zipper/.meta/config.json | 4 +-- 137 files changed, 516 insertions(+), 294 deletions(-) diff --git a/exercises/concept/black-jack/.meta/config.json b/exercises/concept/black-jack/.meta/config.json index f1ff6a193d..a23fa9e8f9 100644 --- a/exercises/concept/black-jack/.meta/config.json +++ b/exercises/concept/black-jack/.meta/config.json @@ -1,11 +1,27 @@ { - "blurb": "Learn about comparisons by implementing some Black Jack judging rules.", - "authors": ["Ticktakto", "Yabby1997", "limm-jk", "OMEGA-Y", "wnstj2007", "pranasziaukas", "bethanyG"], - "icon": "poker", - "contributors": ["PaulT89"], + "authors": [ + "Ticktakto", + "Yabby1997", + "limm-jk", + "OMEGA-Y", + "wnstj2007", + "pranasziaukas", + "bethanyG" + ], + "contributors": [ + "PaulT89" + ], "files": { - "solution": ["black_jack.py"], - "test": ["black_jack_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "black_jack.py" + ], + "test": [ + "black_jack_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "poker", + "blurb": "Learn about comparisons by implementing some Black Jack judging rules." } diff --git a/exercises/concept/card-games/.meta/config.json b/exercises/concept/card-games/.meta/config.json index 8e5645485a..f7d39caa01 100644 --- a/exercises/concept/card-games/.meta/config.json +++ b/exercises/concept/card-games/.meta/config.json @@ -1,11 +1,24 @@ { - "blurb": "Learn about lists by tracking hands in card games.", - "icon": "poker", - "contributors": ["valentin-p", "pranasziaukas"], - "authors": ["itamargal", "isaacg", "bethanyg"], + "authors": [ + "itamargal", + "isaacg", + "bethanyg" + ], + "contributors": [ + "valentin-p", + "pranasziaukas" + ], "files": { - "solution": ["lists.py"], - "test": ["lists_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "lists.py" + ], + "test": [ + "lists_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "poker", + "blurb": "Learn about lists by tracking hands in card games." } diff --git a/exercises/concept/cater-waiter/.meta/config.json b/exercises/concept/cater-waiter/.meta/config.json index c5f7635f0f..89145f52b7 100644 --- a/exercises/concept/cater-waiter/.meta/config.json +++ b/exercises/concept/cater-waiter/.meta/config.json @@ -1,12 +1,25 @@ { - "blurb": "Learn about sets by managing the menus and ingredients for your catering company's event.", - "icon": "meetup", - "authors": ["bethanyg"], - "contributors": ["zepam"], + "authors": [ + "bethanyg" + ], + "contributors": [ + "zepam" + ], "files": { - "solution": ["sets.py"], - "test": ["sets_test.py"], - "exemplar": [".meta/exemplar.py"], - "editor": ["sets_test_data.py", "sets_categories_data.py"] - } + "solution": [ + "sets.py" + ], + "test": [ + "sets_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ], + "editor": [ + "sets_test_data.py", + "sets_categories_data.py" + ] + }, + "icon": "meetup", + "blurb": "Learn about sets by managing the menus and ingredients for your catering company's event." } diff --git a/exercises/concept/chaitanas-colossal-coaster/.meta/config.json b/exercises/concept/chaitanas-colossal-coaster/.meta/config.json index f5acc99ec0..335d2f9d7d 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.meta/config.json +++ b/exercises/concept/chaitanas-colossal-coaster/.meta/config.json @@ -1,11 +1,23 @@ { - "blurb": "Learn useful list methods helping Chaitana manage the lines for her colossal roller coaster.", - "icon": "spiral-matrix", - "contributors": ["valentin-p", "pranasziaukas"], - "authors": ["mohanrajanr", "BethanyG"], + "authors": [ + "mohanrajanr", + "BethanyG" + ], + "contributors": [ + "valentin-p", + "pranasziaukas" + ], "files": { - "solution": ["list_methods.py"], - "test": ["list_methods_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "list_methods.py" + ], + "test": [ + "list_methods_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "spiral-matrix", + "blurb": "Learn useful list methods helping Chaitana manage the lines for her colossal roller coaster." } diff --git a/exercises/concept/currency-exchange/.meta/config.json b/exercises/concept/currency-exchange/.meta/config.json index e002690297..c9825d3c60 100644 --- a/exercises/concept/currency-exchange/.meta/config.json +++ b/exercises/concept/currency-exchange/.meta/config.json @@ -1,11 +1,26 @@ { - "blurb": "Learn about numbers by solving Chandler's currency exchange conundrums.", - "icon": "hyperia-forex", - "authors": ["Ticktakto", "Yabby1997", "limm-jk", "OMEGA-Y", "wnstj2007", "J08K"], - "contributors": ["pranasziaukas"], + "authors": [ + "Ticktakto", + "Yabby1997", + "limm-jk", + "OMEGA-Y", + "wnstj2007", + "J08K" + ], + "contributors": [ + "pranasziaukas" + ], "files": { - "solution": ["exchange.py"], - "test": ["exchange_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "exchange.py" + ], + "test": [ + "exchange_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "hyperia-forex", + "blurb": "Learn about numbers by solving Chandler's currency exchange conundrums." } diff --git a/exercises/concept/ellens-alien-game/.meta/config.json b/exercises/concept/ellens-alien-game/.meta/config.json index 92ed8179c6..111ebdc6ad 100644 --- a/exercises/concept/ellens-alien-game/.meta/config.json +++ b/exercises/concept/ellens-alien-game/.meta/config.json @@ -1,11 +1,24 @@ { - "blurb": "Learn about classes by creating an Alien for Ellen's game.", - "icon": "character-study", - "authors": ["PaulT89", "BethanyG"], - "contributors": ["DjangoFett", "kotp", "IsaacG"], + "authors": [ + "PaulT89", + "BethanyG" + ], + "contributors": [ + "DjangoFett", + "kotp", + "IsaacG" + ], "files": { - "solution": ["classes.py"], - "test": ["classes_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "classes.py" + ], + "test": [ + "classes_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "character-study", + "blurb": "Learn about classes by creating an Alien for Ellen's game." } diff --git a/exercises/concept/ghost-gobble-arcade-game/.meta/config.json b/exercises/concept/ghost-gobble-arcade-game/.meta/config.json index a304f1f67a..320ea64677 100644 --- a/exercises/concept/ghost-gobble-arcade-game/.meta/config.json +++ b/exercises/concept/ghost-gobble-arcade-game/.meta/config.json @@ -1,13 +1,26 @@ { - "blurb": "Learn about bools by setting up the rules for the Ghost Gobble arcade game.", - "icon": "matching-brackets", - "authors": ["neenjaw"], - "contributors": ["cmccandless", "bethanyg"], + "authors": [ + "neenjaw" + ], + "contributors": [ + "cmccandless", + "bethanyg" + ], "files": { - "solution": ["arcade_game.py"], - "test": ["arcade_game_test.py"], - "exemplar": [".meta/exemplar.py"] + "solution": [ + "arcade_game.py" + ], + "test": [ + "arcade_game_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] }, - "forked_from": ["elixir/pacman-rules"], - "language_versions": ">=3.5" + "language_versions": ">=3.5", + "forked_from": [ + "elixir/pacman-rules" + ], + "icon": "matching-brackets", + "blurb": "Learn about bools by setting up the rules for the Ghost Gobble arcade game." } diff --git a/exercises/concept/guidos-gorgeous-lasagna/.meta/config.json b/exercises/concept/guidos-gorgeous-lasagna/.meta/config.json index 457426ce9d..f6de0d57f1 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.meta/config.json +++ b/exercises/concept/guidos-gorgeous-lasagna/.meta/config.json @@ -1,11 +1,22 @@ { - "blurb": "Learn the basics of Python by cooking Guido's Gorgeous Lasagna.", - "icon": "lasagna", - "authors": ["BethanyG"], + "authors": [ + "BethanyG" + ], "files": { - "solution": ["lasagna.py"], - "test": ["lasagna_test.py"], - "exemplar": [".meta/exemplar.py"] + "solution": [ + "lasagna.py" + ], + "test": [ + "lasagna_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] }, - "forked_from": ["csharp/lucians-luscious-lasagna", "ruby/lasagna"] + "forked_from": [ + "csharp/lucians-luscious-lasagna", + "ruby/lasagna" + ], + "icon": "lasagna", + "blurb": "Learn the basics of Python by cooking Guido's Gorgeous Lasagna." } diff --git a/exercises/concept/inventory-management/.meta/config.json b/exercises/concept/inventory-management/.meta/config.json index 1f0fb8e1a7..c08624ae42 100644 --- a/exercises/concept/inventory-management/.meta/config.json +++ b/exercises/concept/inventory-management/.meta/config.json @@ -1,11 +1,24 @@ { - "blurb": "Learn about dicts by managing warehouse inventory.", - "icon": "high-scores", - "authors": ["j08k"], - "contributors": ["valentin-p", "bethanyG", "mukeshgurpude", "kotp"], + "authors": [ + "j08k" + ], + "contributors": [ + "valentin-p", + "bethanyG", + "mukeshgurpude", + "kotp" + ], "files": { - "solution": ["dicts.py"], - "test": ["dicts_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "dicts.py" + ], + "test": [ + "dicts_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "high-scores", + "blurb": "Learn about dicts by managing warehouse inventory." } diff --git a/exercises/concept/little-sisters-essay/.meta/config.json b/exercises/concept/little-sisters-essay/.meta/config.json index 708b0c992e..ea803001dd 100644 --- a/exercises/concept/little-sisters-essay/.meta/config.json +++ b/exercises/concept/little-sisters-essay/.meta/config.json @@ -1,11 +1,22 @@ { - "blurb": "Learn about string methods while improving your little sister's school essay.", - "icon": "anagrams", - "contributors": ["valentin-p", "bethanyg"], - "authors": ["kimolivia"], + "authors": [ + "kimolivia" + ], + "contributors": [ + "valentin-p", + "bethanyg" + ], "files": { - "solution": ["string_methods.py"], - "test": ["string_methods_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "string_methods.py" + ], + "test": [ + "string_methods_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "anagrams", + "blurb": "Learn about string methods while improving your little sister's school essay." } diff --git a/exercises/concept/little-sisters-vocab/.meta/config.json b/exercises/concept/little-sisters-vocab/.meta/config.json index d7eecdcea9..c18c248b8c 100644 --- a/exercises/concept/little-sisters-vocab/.meta/config.json +++ b/exercises/concept/little-sisters-vocab/.meta/config.json @@ -1,10 +1,19 @@ { - "blurb": "Learn about strings by helping your little sister with her vocabulary homework.", - "icon": "two-fer", - "authors": ["aldraco", "bethanyg"], + "authors": [ + "aldraco", + "bethanyg" + ], "files": { - "solution": ["strings.py"], - "test": ["strings_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "strings.py" + ], + "test": [ + "strings_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "two-fer", + "blurb": "Learn about strings by helping your little sister with her vocabulary homework." } diff --git a/exercises/concept/locomotive-engineer/.meta/config.json b/exercises/concept/locomotive-engineer/.meta/config.json index 4c10c88263..e9110e93a0 100644 --- a/exercises/concept/locomotive-engineer/.meta/config.json +++ b/exercises/concept/locomotive-engineer/.meta/config.json @@ -1,12 +1,25 @@ { - "blurb": "Learn about unpacking and multiple assignment in Python while helping Linus with his train control system.", - "icon": "tracks-on-tracks-on-tracks", - "authors": ["meatball133","BethanyG"], - "contributors": ["IsaacG"], + "authors": [ + "meatball133", + "BethanyG" + ], + "contributors": [ + "IsaacG" + ], "files": { - "solution": ["locomotive_engineer.py"], - "test": ["locomotive_engineer_test.py"], - "exemplar": [".meta/exemplar.py"] + "solution": [ + "locomotive_engineer.py" + ], + "test": [ + "locomotive_engineer_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] }, - "forked_from": ["javascript/train-driver"] + "forked_from": [ + "javascript/train-driver" + ], + "icon": "tracks-on-tracks-on-tracks", + "blurb": "Learn about unpacking and multiple assignment in Python while helping Linus with his train control system." } diff --git a/exercises/concept/log-levels/.meta/config.json b/exercises/concept/log-levels/.meta/config.json index 8cb94aec2a..fc14d0ef3e 100644 --- a/exercises/concept/log-levels/.meta/config.json +++ b/exercises/concept/log-levels/.meta/config.json @@ -1,12 +1,24 @@ { - "blurb": "Learn about enums by automating your tedious logging-level tasks.", - "icon": "log-levels", - "contributors": ["valentin-p"], - "authors": ["mohanrajanr"], + "authors": [ + "mohanrajanr" + ], + "contributors": [ + "valentin-p" + ], "files": { - "solution": ["enums.py"], - "test": ["enums_test.py"], - "exemplar": [".meta/exemplar.py"] + "solution": [ + "enums.py" + ], + "test": [ + "enums_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] }, - "forked_from": ["csharp/logs-logs-logs"] + "forked_from": [ + "csharp/logs-logs-logs" + ], + "icon": "log-levels", + "blurb": "Learn about enums by automating your tedious logging-level tasks." } diff --git a/exercises/concept/making-the-grade/.meta/config.json b/exercises/concept/making-the-grade/.meta/config.json index 8e1788ab62..d9edd46638 100644 --- a/exercises/concept/making-the-grade/.meta/config.json +++ b/exercises/concept/making-the-grade/.meta/config.json @@ -1,11 +1,22 @@ { - "blurb": "Learn about loops by grading and organizing your students exam scores.", - "icon": "grep", - "authors": ["mohanrajanr", "BethanyG"], - "contributors": ["pranasziaukas"], + "authors": [ + "mohanrajanr", + "BethanyG" + ], + "contributors": [ + "pranasziaukas" + ], "files": { - "solution": ["loops.py"], - "test": ["loops_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "loops.py" + ], + "test": [ + "loops_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "grep", + "blurb": "Learn about loops by grading and organizing your students exam scores." } diff --git a/exercises/concept/meltdown-mitigation/.meta/config.json b/exercises/concept/meltdown-mitigation/.meta/config.json index 78c54743d0..eb88d8b411 100644 --- a/exercises/concept/meltdown-mitigation/.meta/config.json +++ b/exercises/concept/meltdown-mitigation/.meta/config.json @@ -1,11 +1,22 @@ { - "blurb": "Learn about conditionals and avoid a meltdown by developing a simple control system for a Nuclear Reactor.", - "icon": "circular-buffer", - "authors": ["sachsom95", "BethanyG"], - "contributors": ["kbuc"], + "authors": [ + "sachsom95", + "BethanyG" + ], + "contributors": [ + "kbuc" + ], "files": { - "solution": ["conditionals.py"], - "test": ["conditionals_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "conditionals.py" + ], + "test": [ + "conditionals_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "circular-buffer", + "blurb": "Learn about conditionals and avoid a meltdown by developing a simple control system for a Nuclear Reactor." } diff --git a/exercises/concept/plane-tickets/.meta/config.json b/exercises/concept/plane-tickets/.meta/config.json index 96559aa24c..016ff0cef2 100644 --- a/exercises/concept/plane-tickets/.meta/config.json +++ b/exercises/concept/plane-tickets/.meta/config.json @@ -1,11 +1,21 @@ { - "blurb": "Learn about generators by assigning seats to passengers.", - "authors": ["J08K"], - "icon": "poker", - "contributors": ["BethanyG"], + "authors": [ + "J08K" + ], + "contributors": [ + "BethanyG" + ], "files": { - "solution": ["plane_tickets.py"], - "test": ["plane_tickets_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "plane_tickets.py" + ], + "test": [ + "plane_tickets_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "poker", + "blurb": "Learn about generators by assigning seats to passengers." } diff --git a/exercises/concept/pretty-leaflet/.meta/config.json b/exercises/concept/pretty-leaflet/.meta/config.json index 9b5b07c416..432e26f349 100644 --- a/exercises/concept/pretty-leaflet/.meta/config.json +++ b/exercises/concept/pretty-leaflet/.meta/config.json @@ -1,11 +1,22 @@ { - "blurb": "Learn about string formatting by \"prettifying\" promotional leaflets.", - "icon": "scale-generator", - "contributors": ["j08k", "BethanyG"], - "authors": ["valentin-p"], + "authors": [ + "valentin-p" + ], + "contributors": [ + "j08k", + "BethanyG" + ], "files": { - "solution": ["string_formatting.py"], - "test": ["string_formatting_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "string_formatting.py" + ], + "test": [ + "string_formatting_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "scale-generator", + "blurb": "Learn about string formatting by \"prettifying\" promotional leaflets." } diff --git a/exercises/concept/restaurant-rozalynn/.meta/config.json b/exercises/concept/restaurant-rozalynn/.meta/config.json index 3c4cfed0b6..5b18149648 100644 --- a/exercises/concept/restaurant-rozalynn/.meta/config.json +++ b/exercises/concept/restaurant-rozalynn/.meta/config.json @@ -1,10 +1,19 @@ { - "blurb": "Learn about None by managing the reservations at Restaurant Rozalynn.", - "icon": "kindergarten-garden", - "authors": ["mohanrajanr", "BethanyG"], + "authors": [ + "mohanrajanr", + "BethanyG" + ], "files": { - "solution": ["none.py"], - "test": ["none_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "none.py" + ], + "test": [ + "none_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "kindergarten-garden", + "blurb": "Learn about None by managing the reservations at Restaurant Rozalynn." } diff --git a/exercises/concept/tisbury-treasure-hunt/.meta/config.json b/exercises/concept/tisbury-treasure-hunt/.meta/config.json index 3a8c476fe9..f3d1ad373f 100644 --- a/exercises/concept/tisbury-treasure-hunt/.meta/config.json +++ b/exercises/concept/tisbury-treasure-hunt/.meta/config.json @@ -1,10 +1,18 @@ { - "blurb": "Learn about tuples by helping out competitors in the Tisbury Treasure Hunt.", - "icon": "tisbury-treasure-hunt", - "authors": ["bethanyg"], + "authors": [ + "bethanyg" + ], "files": { - "solution": ["tuples.py"], - "test": ["tuples_test.py"], - "exemplar": [".meta/exemplar.py"] - } + "solution": [ + "tuples.py" + ], + "test": [ + "tuples_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "tisbury-treasure-hunt", + "blurb": "Learn about tuples by helping out competitors in the Tisbury Treasure Hunt." } diff --git a/exercises/practice/acronym/.meta/config.json b/exercises/practice/acronym/.meta/config.json index cac0adf967..e29fff682f 100644 --- a/exercises/practice/acronym/.meta/config.json +++ b/exercises/practice/acronym/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Convert a long phrase to its acronym.", "authors": [ "rootulp" ], @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "Convert a long phrase to its acronym.", "source": "Julien Vanier", "source_url": "https://github.com/monkbroc" } diff --git a/exercises/practice/affine-cipher/.meta/config.json b/exercises/practice/affine-cipher/.meta/config.json index fb6606be5d..3d23ccaaef 100644 --- a/exercises/practice/affine-cipher/.meta/config.json +++ b/exercises/practice/affine-cipher/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.", "authors": [ "jackattack24" ], @@ -20,6 +19,7 @@ ".meta/example.py" ] }, + "blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Affine_cipher" } diff --git a/exercises/practice/all-your-base/.meta/config.json b/exercises/practice/all-your-base/.meta/config.json index 6862f65307..57499d1e20 100644 --- a/exercises/practice/all-your-base/.meta/config.json +++ b/exercises/practice/all-your-base/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Convert a number, represented as a sequence of digits in one base, to any other base.", "authors": [ "behrtam" ], @@ -23,5 +22,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Convert a number, represented as a sequence of digits in one base, to any other base." } diff --git a/exercises/practice/allergies/.meta/config.json b/exercises/practice/allergies/.meta/config.json index d38070cec6..b918bdbcce 100644 --- a/exercises/practice/allergies/.meta/config.json +++ b/exercises/practice/allergies/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.", "authors": [ "sjakobi" ], @@ -30,6 +29,7 @@ ".meta/example.py" ] }, + "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.", "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://turing.edu" } diff --git a/exercises/practice/alphametics/.meta/config.json b/exercises/practice/alphametics/.meta/config.json index e609cb5d99..38bc5e3a56 100644 --- a/exercises/practice/alphametics/.meta/config.json +++ b/exercises/practice/alphametics/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Write a function to solve alphametics puzzles.", "authors": [ "behrtam" ], @@ -27,5 +26,6 @@ ".meta/example.py" ] }, - "test_runner": false + "test_runner": false, + "blurb": "Write a function to solve alphametics puzzles." } diff --git a/exercises/practice/anagram/.meta/config.json b/exercises/practice/anagram/.meta/config.json index d989bc7947..6dee5e1551 100644 --- a/exercises/practice/anagram/.meta/config.json +++ b/exercises/practice/anagram/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a word and a list of possible anagrams, select the correct sublist.", "authors": [], "contributors": [ "behrtam", @@ -33,6 +32,7 @@ ".meta/example.py" ] }, + "blurb": "Given a word and a list of possible anagrams, select the correct sublist.", "source": "Inspired by the Extreme Startup game", "source_url": "https://github.com/rchatley/extreme_startup" } diff --git a/exercises/practice/armstrong-numbers/.meta/config.json b/exercises/practice/armstrong-numbers/.meta/config.json index 0490a9f221..2febbd205d 100644 --- a/exercises/practice/armstrong-numbers/.meta/config.json +++ b/exercises/practice/armstrong-numbers/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Determine if a number is an Armstrong number.", "authors": [ "pheanex" ], @@ -22,6 +21,7 @@ ".meta/example.py" ] }, + "blurb": "Determine if a number is an Armstrong number.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Narcissistic_number" } diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json index a1c2467361..9f678c6f20 100644 --- a/exercises/practice/atbash-cipher/.meta/config.json +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.", "authors": [ "betegelse" ], @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Atbash" } diff --git a/exercises/practice/bank-account/.meta/config.json b/exercises/practice/bank-account/.meta/config.json index 06616793dd..8ab9aa0d88 100644 --- a/exercises/practice/bank-account/.meta/config.json +++ b/exercises/practice/bank-account/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money. Watch out for concurrent transactions!", "authors": [], "contributors": [ "cmccandless", @@ -16,5 +15,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money. Watch out for concurrent transactions!" } diff --git a/exercises/practice/beer-song/.meta/config.json b/exercises/practice/beer-song/.meta/config.json index a59b25cd4a..834501ed38 100644 --- a/exercises/practice/beer-song/.meta/config.json +++ b/exercises/practice/beer-song/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.", "authors": [], "contributors": [ "behrtam", @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.", "source": "Learn to Program by Chris Pine", "source_url": "https://pine.fm/LearnToProgram/?Chapter=06" } diff --git a/exercises/practice/binary-search-tree/.meta/config.json b/exercises/practice/binary-search-tree/.meta/config.json index c374f8caab..8750a7497f 100644 --- a/exercises/practice/binary-search-tree/.meta/config.json +++ b/exercises/practice/binary-search-tree/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Insert and search for numbers in a binary tree.", "authors": [ "rodrigocam" ], @@ -22,6 +21,7 @@ ".meta/example.py" ] }, + "blurb": "Insert and search for numbers in a binary tree.", "source": "Josh Cheek", "source_url": "https://twitter.com/josh_cheek" } diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json index cc3fb2823f..8b76b593d1 100644 --- a/exercises/practice/binary-search/.meta/config.json +++ b/exercises/practice/binary-search/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement a binary search algorithm.", "authors": [ "michaelkunc" ], @@ -30,6 +29,7 @@ ".meta/example.py" ] }, + "blurb": "Implement a binary search algorithm.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm" } diff --git a/exercises/practice/bob/.meta/config.json b/exercises/practice/bob/.meta/config.json index 1c91b43760..ec0fc61776 100644 --- a/exercises/practice/bob/.meta/config.json +++ b/exercises/practice/bob/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.", "authors": [], "contributors": [ "0xae", @@ -42,6 +41,7 @@ ".meta/example.py" ] }, + "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.", "source": "Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial.", "source_url": "https://pine.fm/LearnToProgram/?Chapter=06" } diff --git a/exercises/practice/book-store/.meta/config.json b/exercises/practice/book-store/.meta/config.json index ed1c0f5556..87dd6d2cc6 100644 --- a/exercises/practice/book-store/.meta/config.json +++ b/exercises/practice/book-store/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.", "authors": [ "behrtam" ], @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.", "source": "Inspired by the harry potter kata from Cyber-Dojo.", "source_url": "https://cyber-dojo.org" } diff --git a/exercises/practice/bowling/.meta/config.json b/exercises/practice/bowling/.meta/config.json index 475fd3bf71..e97b0e69c8 100644 --- a/exercises/practice/bowling/.meta/config.json +++ b/exercises/practice/bowling/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Score a bowling game.", "authors": [ "aes421" ], @@ -22,6 +21,7 @@ ".meta/example.py" ] }, + "blurb": "Score a bowling game.", "source": "The Bowling Game Kata from UncleBob", "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" } diff --git a/exercises/practice/change/.meta/config.json b/exercises/practice/change/.meta/config.json index 191ea1fede..2d117feec9 100644 --- a/exercises/practice/change/.meta/config.json +++ b/exercises/practice/change/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Correctly determine change to be given using the least number of coins.", "authors": [ "justani" ], @@ -26,6 +25,7 @@ ".meta/example.py" ] }, + "blurb": "Correctly determine change to be given using the least number of coins.", "source": "Software Craftsmanship - Coin Change Kata", "source_url": "https://web.archive.org/web/20130115115225/http://craftsmanship.sv.cmu.edu:80/exercises/coin-change-kata" } diff --git a/exercises/practice/circular-buffer/.meta/config.json b/exercises/practice/circular-buffer/.meta/config.json index ba40c18f70..09fc60b45d 100644 --- a/exercises/practice/circular-buffer/.meta/config.json +++ b/exercises/practice/circular-buffer/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.", "authors": [ "behrtam" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Circular_buffer" } diff --git a/exercises/practice/clock/.meta/config.json b/exercises/practice/clock/.meta/config.json index 441a88c49a..0f1b8fa94b 100644 --- a/exercises/practice/clock/.meta/config.json +++ b/exercises/practice/clock/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement a clock that handles times without dates.", "authors": [ "chrisftw" ], @@ -30,6 +29,7 @@ ".meta/example.py" ] }, + "blurb": "Implement a clock that handles times without dates.", "source": "Pairing session with Erin Drummond", "source_url": "https://twitter.com/ebdrummond" } diff --git a/exercises/practice/collatz-conjecture/.meta/config.json b/exercises/practice/collatz-conjecture/.meta/config.json index 93ed61f514..e5eda73e1d 100644 --- a/exercises/practice/collatz-conjecture/.meta/config.json +++ b/exercises/practice/collatz-conjecture/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.", "authors": [ "zwaltman" ], @@ -26,6 +25,7 @@ ".meta/example.py" ] }, + "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.", "source": "An unsolved problem in mathematics named after mathematician Lothar Collatz", "source_url": "https://en.wikipedia.org/wiki/3x_%2B_1_problem" } diff --git a/exercises/practice/complex-numbers/.meta/config.json b/exercises/practice/complex-numbers/.meta/config.json index 55a9efb45b..b79e6544a5 100644 --- a/exercises/practice/complex-numbers/.meta/config.json +++ b/exercises/practice/complex-numbers/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement complex numbers.", "authors": [], "contributors": [ "cmccandless", @@ -24,6 +23,7 @@ ".meta/example.py" ] }, + "blurb": "Implement complex numbers.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Complex_number" } diff --git a/exercises/practice/connect/.meta/config.json b/exercises/practice/connect/.meta/config.json index b8ca64254d..dcabaa7b8f 100644 --- a/exercises/practice/connect/.meta/config.json +++ b/exercises/practice/connect/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Compute the result for a game of Hex / Polygon.", "authors": [ "yunchih" ], @@ -21,5 +20,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Compute the result for a game of Hex / Polygon." } diff --git a/exercises/practice/crypto-square/.meta/config.json b/exercises/practice/crypto-square/.meta/config.json index c8e34bd051..7c7de94c30 100644 --- a/exercises/practice/crypto-square/.meta/config.json +++ b/exercises/practice/crypto-square/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement the classic method for composing secret messages called a square code.", "authors": [ "betegelse" ], @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Implement the classic method for composing secret messages called a square code.", "source": "J Dalbey's Programming Practice problems", "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/custom-set/.meta/config.json b/exercises/practice/custom-set/.meta/config.json index b3d9a8de5d..f3c73c2b52 100644 --- a/exercises/practice/custom-set/.meta/config.json +++ b/exercises/practice/custom-set/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Create a custom set type.", "authors": [ "cmccandless" ], @@ -18,5 +17,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Create a custom set type." } diff --git a/exercises/practice/darts/.meta/config.json b/exercises/practice/darts/.meta/config.json index 294ae20160..a306b38c15 100644 --- a/exercises/practice/darts/.meta/config.json +++ b/exercises/practice/darts/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Write a function that returns the earned points in a single toss of a Darts game.", "authors": [ "GascaK" ], @@ -25,5 +24,6 @@ ".meta/example.py" ] }, + "blurb": "Write a function that returns the earned points in a single toss of a Darts game.", "source": "Inspired by an exercise created by a professor Della Paolera in Argentina" } diff --git a/exercises/practice/diamond/.meta/config.json b/exercises/practice/diamond/.meta/config.json index 0108dfaa9b..f349d659cb 100644 --- a/exercises/practice/diamond/.meta/config.json +++ b/exercises/practice/diamond/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.", "authors": [ "behrtam" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.", "source": "Seb Rose", "source_url": "https://web.archive.org/web/20220807163751/http://claysnow.co.uk/recycling-tests-in-tdd/" } diff --git a/exercises/practice/difference-of-squares/.meta/config.json b/exercises/practice/difference-of-squares/.meta/config.json index 3dc17de673..fe714d49db 100644 --- a/exercises/practice/difference-of-squares/.meta/config.json +++ b/exercises/practice/difference-of-squares/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", "authors": [ "sjakobi" ], @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", "source": "Problem 6 at Project Euler", "source_url": "https://projecteuler.net/problem=6" } diff --git a/exercises/practice/diffie-hellman/.meta/config.json b/exercises/practice/diffie-hellman/.meta/config.json index feb9623d4e..81e8cb85bd 100644 --- a/exercises/practice/diffie-hellman/.meta/config.json +++ b/exercises/practice/diffie-hellman/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Diffie-Hellman key exchange.", "authors": [], "contributors": [ "cmccandless", @@ -20,6 +19,7 @@ ".meta/example.py" ] }, + "blurb": "Diffie-Hellman key exchange.", "source": "Wikipedia, 1024 bit key from www.cryptopp.com/wiki.", "source_url": "https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" } diff --git a/exercises/practice/dnd-character/.meta/config.json b/exercises/practice/dnd-character/.meta/config.json index 087026ecb4..8f2c3108d9 100644 --- a/exercises/practice/dnd-character/.meta/config.json +++ b/exercises/practice/dnd-character/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Randomly generate Dungeons & Dragons characters.", "authors": [ "GascaK" ], @@ -20,6 +19,7 @@ ".meta/example.py" ] }, + "blurb": "Randomly generate Dungeons & Dragons characters.", "source": "Simon Shine, Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945" } diff --git a/exercises/practice/dominoes/.meta/config.json b/exercises/practice/dominoes/.meta/config.json index 0a0d049c46..2b7746f65b 100644 --- a/exercises/practice/dominoes/.meta/config.json +++ b/exercises/practice/dominoes/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Make a chain of dominoes.", "authors": [ "cmccandless" ], @@ -21,5 +20,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Make a chain of dominoes." } diff --git a/exercises/practice/dot-dsl/.meta/config.json b/exercises/practice/dot-dsl/.meta/config.json index b285955f86..928e8b2aa7 100644 --- a/exercises/practice/dot-dsl/.meta/config.json +++ b/exercises/practice/dot-dsl/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Write a Domain Specific Language similar to the Graphviz dot language.", "authors": [ "ackerleytng" ], @@ -22,6 +21,7 @@ ".meta/example.py" ] }, + "blurb": "Write a Domain Specific Language similar to the Graphviz dot language.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/DOT_(graph_description_language)" } diff --git a/exercises/practice/etl/.meta/config.json b/exercises/practice/etl/.meta/config.json index 777e51cb84..4d99e6314d 100644 --- a/exercises/practice/etl/.meta/config.json +++ b/exercises/practice/etl/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "We are going to do the `Transform` step of an Extract-Transform-Load.", "authors": [], "contributors": [ "alirezaghey", @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "We are going to do the `Transform` step of an Extract-Transform-Load.", "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://turing.edu" } diff --git a/exercises/practice/flatten-array/.meta/config.json b/exercises/practice/flatten-array/.meta/config.json index 818addda38..bd0ec35c03 100644 --- a/exercises/practice/flatten-array/.meta/config.json +++ b/exercises/practice/flatten-array/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Take a nested list and return a single list with all values except nil/null.", "authors": [ "treyhunner" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Take a nested list and return a single list with all values except nil/null.", "source": "Interview Question", "source_url": "https://reference.wolfram.com/language/ref/Flatten.html" } diff --git a/exercises/practice/food-chain/.meta/config.json b/exercises/practice/food-chain/.meta/config.json index 1f359e9b0e..4a42e70766 100644 --- a/exercises/practice/food-chain/.meta/config.json +++ b/exercises/practice/food-chain/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'.", "authors": [ "luanjpb" ], @@ -21,6 +20,7 @@ ".meta/example.py" ] }, + "blurb": "Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/There_Was_an_Old_Lady_Who_Swallowed_a_Fly" } diff --git a/exercises/practice/forth/.meta/config.json b/exercises/practice/forth/.meta/config.json index 3cc0263fe5..569eddd367 100644 --- a/exercises/practice/forth/.meta/config.json +++ b/exercises/practice/forth/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement an evaluator for a very simple subset of Forth.", "authors": [ "cmccandless" ], @@ -23,5 +22,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Implement an evaluator for a very simple subset of Forth." } diff --git a/exercises/practice/gigasecond/.meta/config.json b/exercises/practice/gigasecond/.meta/config.json index b0af657f37..59b2d30cc9 100644 --- a/exercises/practice/gigasecond/.meta/config.json +++ b/exercises/practice/gigasecond/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a moment, determine the moment that would be after a gigasecond has passed.", "authors": [], "contributors": [ "behrtam", @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Given a moment, determine the moment that would be after a gigasecond has passed.", "source": "Chapter 9 in Chris Pine's online Learn to Program tutorial.", "source_url": "https://pine.fm/LearnToProgram/?Chapter=09" } diff --git a/exercises/practice/go-counting/.meta/config.json b/exercises/practice/go-counting/.meta/config.json index 06e04c3431..0cfd3ff8bc 100644 --- a/exercises/practice/go-counting/.meta/config.json +++ b/exercises/practice/go-counting/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Count the scored points on a Go board.", "authors": [ "yunchih" ], @@ -21,5 +20,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Count the scored points on a Go board." } diff --git a/exercises/practice/grade-school/.meta/config.json b/exercises/practice/grade-school/.meta/config.json index 6fa1b39a15..1a93a3e238 100644 --- a/exercises/practice/grade-school/.meta/config.json +++ b/exercises/practice/grade-school/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given students' names along with the grade that they are in, create a roster for the school.", "authors": [], "contributors": [ "behrtam", @@ -28,5 +27,6 @@ ".meta/example.py" ] }, + "blurb": "Given students' names along with the grade that they are in, create a roster for the school.", "source": "A pairing session with Phil Battos at gSchool" } diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json index 12b2a81dca..4e59df7431 100644 --- a/exercises/practice/grains/.meta/config.json +++ b/exercises/practice/grains/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", "authors": [], "contributors": [ "behrtam", @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", "source": "The CodeRanch Cattle Drive, Assignment 6", "source_url": "https://coderanch.com/wiki/718824/Grains" } diff --git a/exercises/practice/grep/.meta/config.json b/exercises/practice/grep/.meta/config.json index 57c0ec8e07..b79d15e0c7 100644 --- a/exercises/practice/grep/.meta/config.json +++ b/exercises/practice/grep/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.", "authors": [ "behrtam" ], @@ -26,6 +25,7 @@ ".meta/example.py" ] }, + "blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.", "source": "Conversation with Nate Foster.", "source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf" } diff --git a/exercises/practice/hamming/.meta/config.json b/exercises/practice/hamming/.meta/config.json index 34b97637ba..1280ded912 100644 --- a/exercises/practice/hamming/.meta/config.json +++ b/exercises/practice/hamming/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Calculate the Hamming difference between two DNA strands.", "authors": [ "betegelse" ], @@ -32,6 +31,7 @@ ".meta/example.py" ] }, + "blurb": "Calculate the Hamming difference between two DNA strands.", "source": "The Calculating Point Mutations problem at Rosalind", "source_url": "https://rosalind.info/problems/hamm/" } diff --git a/exercises/practice/hangman/.meta/config.json b/exercises/practice/hangman/.meta/config.json index acd8ab23c6..1d05148753 100644 --- a/exercises/practice/hangman/.meta/config.json +++ b/exercises/practice/hangman/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement the logic of the hangman game using functional reactive programming.", "authors": [ "junming403" ], @@ -19,5 +18,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Implement the logic of the hangman game using functional reactive programming." } diff --git a/exercises/practice/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json index c3520cfdee..fc3599fd07 100644 --- a/exercises/practice/hello-world/.meta/config.json +++ b/exercises/practice/hello-world/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "The classical introductory exercise. Just say \"Hello, World!\".", "authors": [ "michaelem" ], @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "The classical introductory exercise. Just say \"Hello, World!\".", "source": "This is an exercise to introduce users to using Exercism", "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" } diff --git a/exercises/practice/high-scores/.meta/config.json b/exercises/practice/high-scores/.meta/config.json index 37ec344ff5..c712113158 100644 --- a/exercises/practice/high-scores/.meta/config.json +++ b/exercises/practice/high-scores/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Manage a player's High Score list.", "authors": [ "behrtam" ], @@ -25,5 +24,6 @@ ".meta/example.py" ] }, + "blurb": "Manage a player's High Score list.", "source": "Tribute to the eighties' arcade game Frogger" } diff --git a/exercises/practice/house/.meta/config.json b/exercises/practice/house/.meta/config.json index 7de79da60f..328c8c913e 100644 --- a/exercises/practice/house/.meta/config.json +++ b/exercises/practice/house/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Output the nursery rhyme 'This is the House that Jack Built'.", "authors": [ "betegelse" ], @@ -26,6 +25,7 @@ ".meta/example.py" ] }, + "blurb": "Output the nursery rhyme 'This is the House that Jack Built'.", "source": "British nursery rhyme", "source_url": "https://en.wikipedia.org/wiki/This_Is_The_House_That_Jack_Built" } diff --git a/exercises/practice/isbn-verifier/.meta/config.json b/exercises/practice/isbn-verifier/.meta/config.json index 9225f76825..78b6dcceb1 100644 --- a/exercises/practice/isbn-verifier/.meta/config.json +++ b/exercises/practice/isbn-verifier/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Check if a given string is a valid ISBN-10 number.", "authors": [ "pheanex" ], @@ -22,6 +21,7 @@ ".meta/example.py" ] }, + "blurb": "Check if a given string is a valid ISBN-10 number.", "source": "Converting a string into a number and some basic processing utilizing a relatable real world example.", "source_url": "https://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digit_calculation" } diff --git a/exercises/practice/isogram/.meta/config.json b/exercises/practice/isogram/.meta/config.json index 3c6bcb4e85..302b30a592 100644 --- a/exercises/practice/isogram/.meta/config.json +++ b/exercises/practice/isogram/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Determine if a word or phrase is an isogram.", "authors": [ "behrtam" ], @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "Determine if a word or phrase is an isogram.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Isogram" } diff --git a/exercises/practice/kindergarten-garden/.meta/config.json b/exercises/practice/kindergarten-garden/.meta/config.json index 396235f79d..58767fd390 100644 --- a/exercises/practice/kindergarten-garden/.meta/config.json +++ b/exercises/practice/kindergarten-garden/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a diagram, determine which plants each child in the kindergarten class is responsible for.", "authors": [ "sjakobi" ], @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "Given a diagram, determine which plants each child in the kindergarten class is responsible for.", "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://turing.edu" } diff --git a/exercises/practice/knapsack/.meta/config.json b/exercises/practice/knapsack/.meta/config.json index d76f3d0881..91b48036a2 100644 --- a/exercises/practice/knapsack/.meta/config.json +++ b/exercises/practice/knapsack/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", "authors": [ "omer-g" ], @@ -21,6 +20,7 @@ ".meta/example.py" ] }, + "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Knapsack_problem" } diff --git a/exercises/practice/largest-series-product/.meta/config.json b/exercises/practice/largest-series-product/.meta/config.json index 8b12e5b55c..138f3592ec 100644 --- a/exercises/practice/largest-series-product/.meta/config.json +++ b/exercises/practice/largest-series-product/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", "authors": [ "sjakobi" ], @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", "source": "A variation on Problem 8 at Project Euler", "source_url": "https://projecteuler.net/problem=8" } diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index 687638ae0b..fd2bd24087 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a year, report if it is a leap year.", "authors": [], "contributors": [ "AnAccountForReportingBugs", @@ -33,6 +32,7 @@ ".meta/example.py" ] }, + "blurb": "Given a year, report if it is a leap year.", "source": "CodeRanch Cattle Drive, Assignment 3", "source_url": "https://coderanch.com/t/718816/Leap" } diff --git a/exercises/practice/ledger/.meta/config.json b/exercises/practice/ledger/.meta/config.json index 114fd31056..eeb5e2ccdf 100644 --- a/exercises/practice/ledger/.meta/config.json +++ b/exercises/practice/ledger/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Refactor a ledger printer.", "authors": [ "cmccandless" ], @@ -17,5 +16,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Refactor a ledger printer." } diff --git a/exercises/practice/linked-list/.meta/config.json b/exercises/practice/linked-list/.meta/config.json index e239b92ffd..c2d286f667 100644 --- a/exercises/practice/linked-list/.meta/config.json +++ b/exercises/practice/linked-list/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement a doubly linked list.", "authors": [ "behrtam" ], @@ -23,5 +22,6 @@ ".meta/example.py" ] }, + "blurb": "Implement a doubly linked list.", "source": "Classic computer science topic" } diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json index 91424481b4..efc025a354 100644 --- a/exercises/practice/list-ops/.meta/config.json +++ b/exercises/practice/list-ops/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement basic list operations.", "authors": [ "behrtam" ], @@ -25,5 +24,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Implement basic list operations." } diff --git a/exercises/practice/luhn/.meta/config.json b/exercises/practice/luhn/.meta/config.json index ead518b8e1..c9a7c188e7 100644 --- a/exercises/practice/luhn/.meta/config.json +++ b/exercises/practice/luhn/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a number determine whether or not it is valid per the Luhn formula.", "authors": [ "sjakobi" ], @@ -34,6 +33,7 @@ ".meta/example.py" ] }, + "blurb": "Given a number determine whether or not it is valid per the Luhn formula.", "source": "The Luhn Algorithm on Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm" } diff --git a/exercises/practice/markdown/.meta/config.json b/exercises/practice/markdown/.meta/config.json index c607b6d085..012bed8314 100644 --- a/exercises/practice/markdown/.meta/config.json +++ b/exercises/practice/markdown/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Refactor a Markdown parser.", "authors": [ "Paul-Ilyin" ], @@ -22,5 +21,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Refactor a Markdown parser." } diff --git a/exercises/practice/matching-brackets/.meta/config.json b/exercises/practice/matching-brackets/.meta/config.json index 77af8aa3e4..b07c1abb83 100644 --- a/exercises/practice/matching-brackets/.meta/config.json +++ b/exercises/practice/matching-brackets/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Make sure the brackets and braces all match.", "authors": [ "behrtam" ], @@ -28,5 +27,6 @@ ".meta/example.py" ] }, + "blurb": "Make sure the brackets and braces all match.", "source": "Ginna Baker" } diff --git a/exercises/practice/matrix/.meta/config.json b/exercises/practice/matrix/.meta/config.json index 5b9aaf85ba..544b7bb0d6 100644 --- a/exercises/practice/matrix/.meta/config.json +++ b/exercises/practice/matrix/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a string representing a matrix of numbers, return the rows and columns of that matrix.", "authors": [ "sjakobi" ], @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "Given a string representing a matrix of numbers, return the rows and columns of that matrix.", "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://turing.edu" } diff --git a/exercises/practice/meetup/.meta/config.json b/exercises/practice/meetup/.meta/config.json index 693d87b257..a001dda143 100644 --- a/exercises/practice/meetup/.meta/config.json +++ b/exercises/practice/meetup/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Calculate the date of meetups.", "authors": [ "sjakobi" ], @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Calculate the date of meetups.", "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month", "source_url": "https://twitter.com/copiousfreetime" } diff --git a/exercises/practice/minesweeper/.meta/config.json b/exercises/practice/minesweeper/.meta/config.json index 3e08f9e376..a4f6101f45 100644 --- a/exercises/practice/minesweeper/.meta/config.json +++ b/exercises/practice/minesweeper/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Add the numbers to a minesweeper board.", "authors": [ "betegelse" ], @@ -29,5 +28,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Add the numbers to a minesweeper board." } diff --git a/exercises/practice/nth-prime/.meta/config.json b/exercises/practice/nth-prime/.meta/config.json index c38d07e93a..e4466db8c8 100644 --- a/exercises/practice/nth-prime/.meta/config.json +++ b/exercises/practice/nth-prime/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a number n, determine what the nth prime is.", "authors": [ "sjakobi" ], @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Given a number n, determine what the nth prime is.", "source": "A variation on Problem 7 at Project Euler", "source_url": "https://projecteuler.net/problem=7" } diff --git a/exercises/practice/ocr-numbers/.meta/config.json b/exercises/practice/ocr-numbers/.meta/config.json index 669baa24a0..b37fe0d45f 100644 --- a/exercises/practice/ocr-numbers/.meta/config.json +++ b/exercises/practice/ocr-numbers/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled.", "authors": [ "betegelse" ], @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled.", "source": "Inspired by the Bank OCR kata", "source_url": "https://codingdojo.org/kata/BankOCR/" } diff --git a/exercises/practice/paasio/.meta/config.json b/exercises/practice/paasio/.meta/config.json index b8582c8dce..a9c0b9c0fd 100644 --- a/exercises/practice/paasio/.meta/config.json +++ b/exercises/practice/paasio/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Report network IO statistics.", "authors": [ "AnAccountForReportingBugs" ], @@ -18,6 +17,7 @@ ".meta/example.py" ] }, + "blurb": "Report network IO statistics.", "source": "Brian Matsuo", "source_url": "https://github.com/bmatsuo" } diff --git a/exercises/practice/palindrome-products/.meta/config.json b/exercises/practice/palindrome-products/.meta/config.json index 936f9e8b92..fa833ed0c6 100644 --- a/exercises/practice/palindrome-products/.meta/config.json +++ b/exercises/practice/palindrome-products/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Detect palindrome products in a given range.", "authors": [ "sjakobi" ], @@ -30,6 +29,7 @@ ".meta/example.py" ] }, + "blurb": "Detect palindrome products in a given range.", "source": "Problem 4 at Project Euler", "source_url": "https://projecteuler.net/problem=4" } diff --git a/exercises/practice/pangram/.meta/config.json b/exercises/practice/pangram/.meta/config.json index 952e1f6357..3d57bb9f85 100644 --- a/exercises/practice/pangram/.meta/config.json +++ b/exercises/practice/pangram/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Determine if a sentence is a pangram.", "authors": [ "behrtam" ], @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Determine if a sentence is a pangram.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Pangram" } diff --git a/exercises/practice/perfect-numbers/.meta/config.json b/exercises/practice/perfect-numbers/.meta/config.json index 208b5094ec..a77ccea6e9 100644 --- a/exercises/practice/perfect-numbers/.meta/config.json +++ b/exercises/practice/perfect-numbers/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.", "authors": [ "behrtam" ], @@ -26,6 +25,7 @@ ".meta/example.py" ] }, + "blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.", "source": "Taken from Chapter 2 of Functional Thinking by Neal Ford.", "source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/" } diff --git a/exercises/practice/phone-number/.meta/config.json b/exercises/practice/phone-number/.meta/config.json index db2af4d694..f2fe78bd3f 100644 --- a/exercises/practice/phone-number/.meta/config.json +++ b/exercises/practice/phone-number/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Clean up user-entered phone numbers so that they can be sent SMS messages.", "authors": [], "contributors": [ "AnAccountForReportingBugs", @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Clean up user-entered phone numbers so that they can be sent SMS messages.", "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://turing.edu" } diff --git a/exercises/practice/pig-latin/.meta/config.json b/exercises/practice/pig-latin/.meta/config.json index b7fe96161a..6dd8c462e3 100644 --- a/exercises/practice/pig-latin/.meta/config.json +++ b/exercises/practice/pig-latin/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement a program that translates from English to Pig Latin.", "authors": [ "behrtam" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Implement a program that translates from English to Pig Latin.", "source": "The Pig Latin exercise at Test First Teaching by Ultrasaurus", "source_url": "https://github.com/ultrasaurus/test-first-teaching/blob/master/learn_ruby/pig_latin/" } diff --git a/exercises/practice/poker/.meta/config.json b/exercises/practice/poker/.meta/config.json index 13812c3ea6..7c4301c90b 100644 --- a/exercises/practice/poker/.meta/config.json +++ b/exercises/practice/poker/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Pick the best hand(s) from a list of poker hands.", "authors": [ "MartinDelille" ], @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "Pick the best hand(s) from a list of poker hands.", "source": "Inspired by the training course from Udacity.", "source_url": "https://www.udacity.com/course/viewer#!/c-cs212/" } diff --git a/exercises/practice/pov/.meta/config.json b/exercises/practice/pov/.meta/config.json index 5b7a35254f..cdc96ff934 100644 --- a/exercises/practice/pov/.meta/config.json +++ b/exercises/practice/pov/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Reparent a graph on a selected node.", "authors": [ "cmccandless" ], @@ -21,6 +20,7 @@ ".meta/example.py" ] }, + "blurb": "Reparent a graph on a selected node.", "source": "Adaptation of exercise from 4clojure", "source_url": "https://www.4clojure.com/" } diff --git a/exercises/practice/prime-factors/.meta/config.json b/exercises/practice/prime-factors/.meta/config.json index 1d89a38d6b..f50d3fc5f6 100644 --- a/exercises/practice/prime-factors/.meta/config.json +++ b/exercises/practice/prime-factors/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Compute the prime factors of a given natural number.", "authors": [], "contributors": [ "behrtam", @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "Compute the prime factors of a given natural number.", "source": "The Prime Factors Kata by Uncle Bob", "source_url": "https://web.archive.org/web/20221026171801/http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" } diff --git a/exercises/practice/protein-translation/.meta/config.json b/exercises/practice/protein-translation/.meta/config.json index 77ef86f422..3a2569b1a8 100644 --- a/exercises/practice/protein-translation/.meta/config.json +++ b/exercises/practice/protein-translation/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Translate RNA sequences into proteins.", "authors": [ "cptjackson" ], @@ -26,5 +25,6 @@ ".meta/example.py" ] }, + "blurb": "Translate RNA sequences into proteins.", "source": "Tyler Long" } diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 209e162326..03d35139ac 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product a * b * c.", "authors": [ "betegelse" ], @@ -32,6 +31,7 @@ ".meta/example.py" ] }, + "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product a * b * c.", "source": "Problem 9 at Project Euler", "source_url": "https://projecteuler.net/problem=9" } diff --git a/exercises/practice/queen-attack/.meta/config.json b/exercises/practice/queen-attack/.meta/config.json index a4558a4e5a..91336ad151 100644 --- a/exercises/practice/queen-attack/.meta/config.json +++ b/exercises/practice/queen-attack/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.", "authors": [ "betegelse" ], @@ -32,6 +31,7 @@ ".meta/example.py" ] }, + "blurb": "Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.", "source": "J Dalbey's Programming Practice problems", "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/rail-fence-cipher/.meta/config.json b/exercises/practice/rail-fence-cipher/.meta/config.json index 982acd240a..9a904ef1da 100644 --- a/exercises/practice/rail-fence-cipher/.meta/config.json +++ b/exercises/practice/rail-fence-cipher/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement encoding and decoding for the rail fence cipher.", "authors": [ "behrtam" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Implement encoding and decoding for the rail fence cipher.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Transposition_cipher#Rail_Fence_cipher" } diff --git a/exercises/practice/raindrops/.meta/config.json b/exercises/practice/raindrops/.meta/config.json index 2863a8da7f..463a523712 100644 --- a/exercises/practice/raindrops/.meta/config.json +++ b/exercises/practice/raindrops/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Convert a number to a string, the content of which depends on the number's factors.", "authors": [], "contributors": [ "behrtam", @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "Convert a number to a string, the content of which depends on the number's factors.", "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.", "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz" } diff --git a/exercises/practice/rational-numbers/.meta/config.json b/exercises/practice/rational-numbers/.meta/config.json index 6fb2bce69b..67afa131c2 100644 --- a/exercises/practice/rational-numbers/.meta/config.json +++ b/exercises/practice/rational-numbers/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement rational numbers.", "authors": [], "contributors": [ "AnAccountForReportingBugs", @@ -21,6 +20,7 @@ ".meta/example.py" ] }, + "blurb": "Implement rational numbers.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Rational_number" } diff --git a/exercises/practice/react/.meta/config.json b/exercises/practice/react/.meta/config.json index 9e1d6a2b31..71e3b58ad1 100644 --- a/exercises/practice/react/.meta/config.json +++ b/exercises/practice/react/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement a basic reactive system.", "authors": [ "cmccandless" ], @@ -19,5 +18,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Implement a basic reactive system." } diff --git a/exercises/practice/rectangles/.meta/config.json b/exercises/practice/rectangles/.meta/config.json index a716c2c1e9..4010f7d67a 100644 --- a/exercises/practice/rectangles/.meta/config.json +++ b/exercises/practice/rectangles/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Count the rectangles in an ASCII diagram.", "authors": [], "contributors": [ "aboutroots", @@ -23,5 +22,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Count the rectangles in an ASCII diagram." } diff --git a/exercises/practice/resistor-color-duo/.meta/config.json b/exercises/practice/resistor-color-duo/.meta/config.json index 2384d5aa18..f8177d2b06 100644 --- a/exercises/practice/resistor-color-duo/.meta/config.json +++ b/exercises/practice/resistor-color-duo/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Convert color codes, as used on resistors, to a numeric value.", "authors": [ "ee7" ], @@ -22,6 +21,7 @@ ".meta/example.py" ] }, + "blurb": "Convert color codes, as used on resistors, to a numeric value.", "source": "Maud de Vries, Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/issues/1464" } diff --git a/exercises/practice/resistor-color-trio/.meta/config.json b/exercises/practice/resistor-color-trio/.meta/config.json index 600b6cc227..68dad4cd2a 100644 --- a/exercises/practice/resistor-color-trio/.meta/config.json +++ b/exercises/practice/resistor-color-trio/.meta/config.json @@ -1,11 +1,8 @@ { - "blurb": "Convert color codes, as used on resistors, to a human-readable label.", "authors": [ "meatball133", "bethanyg" ], - "contributors": [ - ], "files": { "solution": [ "resistor_color_trio.py" @@ -17,6 +14,7 @@ ".meta/example.py" ] }, + "blurb": "Convert color codes, as used on resistors, to a human-readable label.", "source": "Maud de Vries, Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/issues/1549" } diff --git a/exercises/practice/resistor-color/.meta/config.json b/exercises/practice/resistor-color/.meta/config.json index 3d1ac09574..08c69e138e 100644 --- a/exercises/practice/resistor-color/.meta/config.json +++ b/exercises/practice/resistor-color/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Convert a resistor band's color to its numeric representation.", "authors": [ "gabriel376" ], @@ -20,6 +19,7 @@ ".meta/example.py" ] }, + "blurb": "Convert a resistor band's color to its numeric representation.", "source": "Maud de Vries, Erik Schierboom", "source_url": "https://github.com/exercism/problem-specifications/issues/1458" } diff --git a/exercises/practice/rest-api/.meta/config.json b/exercises/practice/rest-api/.meta/config.json index 63b86416c5..889bf7831d 100644 --- a/exercises/practice/rest-api/.meta/config.json +++ b/exercises/practice/rest-api/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement a RESTful API for tracking IOUs.", "authors": [ "cmccandless" ], @@ -23,5 +22,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Implement a RESTful API for tracking IOUs." } diff --git a/exercises/practice/reverse-string/.meta/config.json b/exercises/practice/reverse-string/.meta/config.json index c000c4ca73..1df068a958 100644 --- a/exercises/practice/reverse-string/.meta/config.json +++ b/exercises/practice/reverse-string/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Reverse a string.", "authors": [ "sjwarner-bp" ], @@ -23,6 +22,7 @@ ".meta/example.py" ] }, + "blurb": "Reverse a string.", "source": "Introductory challenge to reverse an input string", "source_url": "https://medium.freecodecamp.org/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb" } diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index b310b8c036..636aa7ed31 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a DNA strand, return its RNA Complement Transcription.", "authors": [ "BrianHicks" ], @@ -33,6 +32,7 @@ ".meta/example.py" ] }, + "blurb": "Given a DNA strand, return its RNA Complement Transcription.", "source": "Hyperphysics", "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" } diff --git a/exercises/practice/robot-name/.meta/config.json b/exercises/practice/robot-name/.meta/config.json index 3122086f65..648bc8e9fc 100644 --- a/exercises/practice/robot-name/.meta/config.json +++ b/exercises/practice/robot-name/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Manage robot factory settings.", "authors": [], "contributors": [ "ariso353", @@ -26,5 +25,6 @@ ".meta/example.py" ] }, + "blurb": "Manage robot factory settings.", "source": "A debugging session with Paul Blackwell at gSchool." } diff --git a/exercises/practice/robot-simulator/.meta/config.json b/exercises/practice/robot-simulator/.meta/config.json index 863eb1a9e0..aa08e31202 100644 --- a/exercises/practice/robot-simulator/.meta/config.json +++ b/exercises/practice/robot-simulator/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Write a robot simulator.", "authors": [ "behrtam" ], @@ -27,5 +26,6 @@ ".meta/example.py" ] }, + "blurb": "Write a robot simulator.", "source": "Inspired by an interview question at a famous company." } diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index ea6611e8fb..eab034a9f9 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Write a function to convert from normal numbers to Roman Numerals.", "authors": [], "contributors": [ "behrtam", @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "Write a function to convert from normal numbers to Roman Numerals.", "source": "The Roman Numeral Kata", "source_url": "https://codingdojo.org/kata/RomanNumerals/" } diff --git a/exercises/practice/rotational-cipher/.meta/config.json b/exercises/practice/rotational-cipher/.meta/config.json index 412506fa98..d21c3ca018 100644 --- a/exercises/practice/rotational-cipher/.meta/config.json +++ b/exercises/practice/rotational-cipher/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Create an implementation of the rotational cipher, also sometimes called the Caesar cipher.", "authors": [ "krapes" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Create an implementation of the rotational cipher, also sometimes called the Caesar cipher.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Caesar_cipher" } diff --git a/exercises/practice/run-length-encoding/.meta/config.json b/exercises/practice/run-length-encoding/.meta/config.json index 8b6dc72f95..7d8f389afe 100644 --- a/exercises/practice/run-length-encoding/.meta/config.json +++ b/exercises/practice/run-length-encoding/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement run-length encoding and decoding.", "authors": [ "behrtam" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Implement run-length encoding and decoding.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Run-length_encoding" } diff --git a/exercises/practice/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json index 7deb5f4aaf..ba7ae52c4e 100644 --- a/exercises/practice/saddle-points/.meta/config.json +++ b/exercises/practice/saddle-points/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Detect saddle points in a matrix.", "authors": [ "betegelse" ], @@ -35,6 +34,7 @@ ".meta/example.py" ] }, + "blurb": "Detect saddle points in a matrix.", "source": "J Dalbey's Programming Practice problems", "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/satellite/.meta/config.json b/exercises/practice/satellite/.meta/config.json index 0a7eda0bd7..19dadf3cd9 100644 --- a/exercises/practice/satellite/.meta/config.json +++ b/exercises/practice/satellite/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Rebuild binary trees from pre-order and in-order traversals.", "authors": [ "AnAccountForReportingBugs" ], @@ -21,5 +20,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Rebuild binary trees from pre-order and in-order traversals." } diff --git a/exercises/practice/say/.meta/config.json b/exercises/practice/say/.meta/config.json index 506533a113..1090a04a47 100644 --- a/exercises/practice/say/.meta/config.json +++ b/exercises/practice/say/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", "authors": [ "behrtam" ], @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", "source": "A variation on the JavaRanch CattleDrive, Assignment 4", "source_url": "https://coderanch.com/wiki/718804" } diff --git a/exercises/practice/scale-generator/.meta/config.json b/exercises/practice/scale-generator/.meta/config.json index a359c42fc0..26c07d56d6 100644 --- a/exercises/practice/scale-generator/.meta/config.json +++ b/exercises/practice/scale-generator/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Generate musical scales, given a starting note and a set of intervals.", "authors": [ "cptjackson" ], @@ -23,5 +22,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Generate musical scales, given a starting note and a set of intervals." } diff --git a/exercises/practice/scrabble-score/.meta/config.json b/exercises/practice/scrabble-score/.meta/config.json index dafe376cca..d8ff8fb31c 100644 --- a/exercises/practice/scrabble-score/.meta/config.json +++ b/exercises/practice/scrabble-score/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a word, compute the Scrabble score for that word.", "authors": [], "contributors": [ "behrtam", @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "Given a word, compute the Scrabble score for that word.", "source": "Inspired by the Extreme Startup game", "source_url": "https://github.com/rchatley/extreme_startup" } diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json index ee9aeb52a1..cca237b478 100644 --- a/exercises/practice/secret-handshake/.meta/config.json +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", "authors": [ "betegelse" ], @@ -30,6 +29,7 @@ ".meta/example.py" ] }, + "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", "source": "Bert, in Mary Poppins", "source_url": "https://www.imdb.com/title/tt0058331/quotes/qt0437047" } diff --git a/exercises/practice/series/.meta/config.json b/exercises/practice/series/.meta/config.json index 9953afd602..3faaa9ae20 100644 --- a/exercises/practice/series/.meta/config.json +++ b/exercises/practice/series/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a string of digits, output all the contiguous substrings of length `n` in that string.", "authors": [ "sjakobi" ], @@ -27,6 +26,7 @@ ".meta/example.py" ] }, + "blurb": "Given a string of digits, output all the contiguous substrings of length `n` in that string.", "source": "A subset of the Problem 8 at Project Euler", "source_url": "https://projecteuler.net/problem=8" } diff --git a/exercises/practice/sgf-parsing/.meta/config.json b/exercises/practice/sgf-parsing/.meta/config.json index b8461120b4..284ea0fb2f 100644 --- a/exercises/practice/sgf-parsing/.meta/config.json +++ b/exercises/practice/sgf-parsing/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Parsing a Smart Game Format string.", "authors": [ "cmccandless" ], @@ -22,5 +21,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Parsing a Smart Game Format string." } diff --git a/exercises/practice/sieve/.meta/config.json b/exercises/practice/sieve/.meta/config.json index ccf9424987..7bf97ee2c8 100644 --- a/exercises/practice/sieve/.meta/config.json +++ b/exercises/practice/sieve/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.", "authors": [ "sjakobi" ], @@ -29,6 +28,7 @@ ".meta/example.py" ] }, + "blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.", "source": "Sieve of Eratosthenes at Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" } diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index 2176f407c5..0dc1687acf 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher.", "authors": [ "betegelse" ], @@ -31,6 +30,7 @@ ".meta/example.py" ] }, + "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher.", "source": "Substitution Cipher at Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" } diff --git a/exercises/practice/simple-linked-list/.meta/config.json b/exercises/practice/simple-linked-list/.meta/config.json index 7dd5252443..2fc136a325 100644 --- a/exercises/practice/simple-linked-list/.meta/config.json +++ b/exercises/practice/simple-linked-list/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Write a simple linked list implementation that uses Elements and a List.", "authors": [ "cmccandless" ], @@ -20,6 +19,7 @@ ".meta/example.py" ] }, + "blurb": "Write a simple linked list implementation that uses Elements and a List.", "source": "Inspired by 'Data Structures and Algorithms with Object-Oriented Design Patterns in Ruby', singly linked-lists.", "source_url": "https://web.archive.org/web/20160731005714/http://brpreiss.com/books/opus8/html/page96.html" } diff --git a/exercises/practice/space-age/.meta/config.json b/exercises/practice/space-age/.meta/config.json index 738e0fe39e..174af6bfc3 100644 --- a/exercises/practice/space-age/.meta/config.json +++ b/exercises/practice/space-age/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.", "authors": [], "contributors": [ "abhijitparida", @@ -28,6 +27,7 @@ ".meta/example.py" ] }, + "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.", "source": "Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial.", "source_url": "https://pine.fm/LearnToProgram/?Chapter=01" } diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index 7fedd424c0..84a0d9aa4b 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": " Given the size, return a square matrix of numbers in spiral order.", "authors": [ "chgraef" ], @@ -21,6 +20,7 @@ ".meta/example.py" ] }, + "blurb": " Given the size, return a square matrix of numbers in spiral order.", "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" } diff --git a/exercises/practice/sublist/.meta/config.json b/exercises/practice/sublist/.meta/config.json index 63f5377388..f4aeb47de5 100644 --- a/exercises/practice/sublist/.meta/config.json +++ b/exercises/practice/sublist/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Write a function to determine if a list is a sublist of another list.", "authors": [ "betegelse" ], @@ -30,5 +29,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Write a function to determine if a list is a sublist of another list." } diff --git a/exercises/practice/sum-of-multiples/.meta/config.json b/exercises/practice/sum-of-multiples/.meta/config.json index bc2eba2a1c..cc5f1ce871 100644 --- a/exercises/practice/sum-of-multiples/.meta/config.json +++ b/exercises/practice/sum-of-multiples/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a number, find the sum of all the multiples of particular numbers up to but not including that number.", "authors": [ "sjakobi" ], @@ -31,6 +30,7 @@ ".meta/example.py" ] }, + "blurb": "Given a number, find the sum of all the multiples of particular numbers up to but not including that number.", "source": "A variation on Problem 1 at Project Euler", "source_url": "https://projecteuler.net/problem=1" } diff --git a/exercises/practice/tournament/.meta/config.json b/exercises/practice/tournament/.meta/config.json index 2b6214d536..8f7263e78b 100644 --- a/exercises/practice/tournament/.meta/config.json +++ b/exercises/practice/tournament/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Tally the results of a small football competition.", "authors": [ "behrtam" ], @@ -24,5 +23,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Tally the results of a small football competition." } diff --git a/exercises/practice/transpose/.meta/config.json b/exercises/practice/transpose/.meta/config.json index 44dc8d8364..5eb2b08ef7 100644 --- a/exercises/practice/transpose/.meta/config.json +++ b/exercises/practice/transpose/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Take input text and output it transposed.", "authors": [ "nithin-vijayan" ], @@ -26,6 +25,7 @@ ".meta/example.py" ] }, + "blurb": "Take input text and output it transposed.", "source": "Reddit r/dailyprogrammer challenge #270 [Easy].", "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/4msu2x/challenge_270_easy_transpose_the_input_text" } diff --git a/exercises/practice/tree-building/.meta/config.json b/exercises/practice/tree-building/.meta/config.json index 1e798edb59..00d1da4b4b 100644 --- a/exercises/practice/tree-building/.meta/config.json +++ b/exercises/practice/tree-building/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Refactor a tree building algorithm.", "authors": [ "clapmyhands" ], @@ -19,5 +18,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Refactor a tree building algorithm." } diff --git a/exercises/practice/triangle/.meta/config.json b/exercises/practice/triangle/.meta/config.json index d5264660fe..041bf28ccf 100644 --- a/exercises/practice/triangle/.meta/config.json +++ b/exercises/practice/triangle/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", "authors": [], "contributors": [ "behrtam", @@ -31,6 +30,7 @@ ".meta/example.py" ] }, + "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", "source": "The Ruby Koans triangle project, parts 1 & 2", "source_url": "https://web.archive.org/web/20220831105330/http://rubykoans.com" } diff --git a/exercises/practice/twelve-days/.meta/config.json b/exercises/practice/twelve-days/.meta/config.json index 2ed3689f68..06c0b7011f 100644 --- a/exercises/practice/twelve-days/.meta/config.json +++ b/exercises/practice/twelve-days/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Output the lyrics to 'The Twelve Days of Christmas'.", "authors": [ "sjakobi" ], @@ -26,6 +25,7 @@ ".meta/example.py" ] }, + "blurb": "Output the lyrics to 'The Twelve Days of Christmas'.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/The_Twelve_Days_of_Christmas_(song)" } diff --git a/exercises/practice/two-bucket/.meta/config.json b/exercises/practice/two-bucket/.meta/config.json index 177aedc4e4..9934289f1f 100644 --- a/exercises/practice/two-bucket/.meta/config.json +++ b/exercises/practice/two-bucket/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.", "authors": [ "parthsharma2" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.", "source": "Water Pouring Problem", "source_url": "https://demonstrations.wolfram.com/WaterPouringProblem/" } diff --git a/exercises/practice/two-fer/.meta/config.json b/exercises/practice/two-fer/.meta/config.json index 25f2cb7bea..24e71e76f7 100644 --- a/exercises/practice/two-fer/.meta/config.json +++ b/exercises/practice/two-fer/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Create a sentence of the form \"One for X, one for me.\".", "authors": [ "samwincott" ], @@ -26,5 +25,6 @@ ".meta/example.py" ] }, + "blurb": "Create a sentence of the form \"One for X, one for me.\".", "source_url": "https://github.com/exercism/problem-specifications/issues/757" } diff --git a/exercises/practice/variable-length-quantity/.meta/config.json b/exercises/practice/variable-length-quantity/.meta/config.json index 837cd6305a..dc5843abbf 100644 --- a/exercises/practice/variable-length-quantity/.meta/config.json +++ b/exercises/practice/variable-length-quantity/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Implement variable length quantity encoding and decoding.", "authors": [ "behrtam" ], @@ -25,6 +24,7 @@ ".meta/example.py" ] }, + "blurb": "Implement variable length quantity encoding and decoding.", "source": "A poor Splice developer having to implement MIDI encoding/decoding.", "source_url": "https://splice.com" } diff --git a/exercises/practice/word-count/.meta/config.json b/exercises/practice/word-count/.meta/config.json index ffbe0db476..c7134476f9 100644 --- a/exercises/practice/word-count/.meta/config.json +++ b/exercises/practice/word-count/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a phrase, count the occurrences of each word in that phrase.", "authors": [], "contributors": [ "behrtam", @@ -34,5 +33,6 @@ ".meta/example.py" ] }, + "blurb": "Given a phrase, count the occurrences of each word in that phrase.", "source": "This is a classic toy problem, but we were reminded of it by seeing it in the Go Tour." } diff --git a/exercises/practice/word-search/.meta/config.json b/exercises/practice/word-search/.meta/config.json index 635a740ed5..84c6ecddd2 100644 --- a/exercises/practice/word-search/.meta/config.json +++ b/exercises/practice/word-search/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Create a program to solve a word search puzzle.", "authors": [ "behrtam" ], @@ -23,5 +22,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Create a program to solve a word search puzzle." } diff --git a/exercises/practice/wordy/.meta/config.json b/exercises/practice/wordy/.meta/config.json index 9b388efefe..07c6341707 100644 --- a/exercises/practice/wordy/.meta/config.json +++ b/exercises/practice/wordy/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Parse and evaluate simple math word problems returning the answer as an integer.", "authors": [ "betegelse" ], @@ -35,6 +34,7 @@ ".meta/example.py" ] }, + "blurb": "Parse and evaluate simple math word problems returning the answer as an integer.", "source": "Inspired by one of the generated questions in the Extreme Startup game.", "source_url": "https://github.com/rchatley/extreme_startup" } diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json index 5ae8137622..78c685cb51 100644 --- a/exercises/practice/yacht/.meta/config.json +++ b/exercises/practice/yacht/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Score a single throw of dice in the game Yacht.", "authors": [], "contributors": [ "aaditkamat", @@ -20,6 +19,7 @@ ".meta/example.py" ] }, + "blurb": "Score a single throw of dice in the game Yacht.", "source": "James Kilfiger, using wikipedia", "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)" } diff --git a/exercises/practice/zebra-puzzle/.meta/config.json b/exercises/practice/zebra-puzzle/.meta/config.json index b237517834..b0b2da5f85 100644 --- a/exercises/practice/zebra-puzzle/.meta/config.json +++ b/exercises/practice/zebra-puzzle/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Solve the zebra puzzle.", "authors": [ "sjakobi" ], @@ -24,6 +23,7 @@ ".meta/example.py" ] }, + "blurb": "Solve the zebra puzzle.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Zebra_Puzzle" } diff --git a/exercises/practice/zipper/.meta/config.json b/exercises/practice/zipper/.meta/config.json index 13d91995cb..c26c55507f 100644 --- a/exercises/practice/zipper/.meta/config.json +++ b/exercises/practice/zipper/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Creating a zipper for a binary tree.", "authors": [ "cmccandless" ], @@ -19,5 +18,6 @@ "example": [ ".meta/example.py" ] - } + }, + "blurb": "Creating a zipper for a binary tree." } From 4a5ae757410c6de2b0870ca49b427fd8cc37dd2a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 3 Dec 2022 00:25:20 -0800 Subject: [PATCH 170/826] Update poker from problem specs. --- exercises/practice/poker/.meta/tests.toml | 17 +++++++++++++- exercises/practice/poker/poker_test.py | 27 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/exercises/practice/poker/.meta/tests.toml b/exercises/practice/poker/.meta/tests.toml index 27a1fad29e..76ac892d93 100644 --- a/exercises/practice/poker/.meta/tests.toml +++ b/exercises/practice/poker/.meta/tests.toml @@ -63,6 +63,9 @@ description = "aces can end a straight (10 J Q K A)" [76856b0d-35cd-49ce-a492-fe5db53abc02] description = "aces can start a straight (A 2 3 4 5)" +[e214b7df-dcba-45d3-a2e5-342d8c46c286] +description = "aces cannot be in the middle of a straight (Q K A 2 3)" + [6980c612-bbff-4914-b17a-b044e4e69ea1] description = "both hands with a straight, tie goes to highest ranked card" @@ -96,5 +99,17 @@ description = "with multiple decks, both hands with identical four of a kind, ti [923bd910-dc7b-4f7d-a330-8b42ec10a3ac] description = "straight flush beats four of a kind" +[d9629e22-c943-460b-a951-2134d1b43346] +description = "aces can end a straight flush (10 J Q K A)" + +[05d5ede9-64a5-4678-b8ae-cf4c595dc824] +description = "aces can start a straight flush (A 2 3 4 5)" + +[ad655466-6d04-49e8-a50c-0043c3ac18ff] +description = "aces cannot be in the middle of a straight flush (Q K A 2 3)" + [d0927f70-5aec-43db-aed8-1cbd1b6ee9ad] -description = "both hands have straight flush, tie goes to highest-ranked card" +description = "both hands have a straight flush, tie goes to highest-ranked card" + +[be620e09-0397-497b-ac37-d1d7a4464cfc] +description = "even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush" diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py index 7951cb533a..f436e48120 100644 --- a/exercises/practice/poker/poker_test.py +++ b/exercises/practice/poker/poker_test.py @@ -115,6 +115,11 @@ def test_aces_can_start_a_straight_a_2_3_4_5(self): best_hands(["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"]), ["4D AH 3S 2D 5C"] ) + def test_aces_cannot_be_in_the_middle_of_a_straight_q_k_a_2_3(self): + self.assertEqual( + best_hands(["2C 3D 7H 5H 2S", "QS KH AC 2D 3S"]), ["2C 3D 7H 5H 2S"] + ) + def test_both_hands_with_a_straight_tie_goes_to_highest_ranked_card(self): self.assertEqual( best_hands(["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"]), ["5S 7H 8S 9D 6H"] @@ -178,11 +183,33 @@ def test_straight_flush_beats_four_of_a_kind(self): best_hands(["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"]), ["7S 8S 9S 6S 10S"] ) + def test_aces_can_end_a_straight_flush_10_j_q_k_a(self): + self.assertEqual( + best_hands(["KC AH AS AD AC", "10C JC QC KC AC"]), ["10C JC QC KC AC"] + ) + + def test_aces_can_start_a_straight_flush_a_2_3_4_5(self): + self.assertEqual( + best_hands(["KS AH AS AD AC", "4H AH 3H 2H 5H"]), ["4H AH 3H 2H 5H"] + ) + + def test_aces_cannot_be_in_the_middle_of_a_straight_flush_q_k_a_2_3(self): + self.assertEqual( + best_hands(["2C AC QC 10C KC", "QH KH AH 2H 3H"]), ["2C AC QC 10C KC"] + ) + def test_both_hands_have_a_straight_flush_tie_goes_to_highest_ranked_card(self): self.assertEqual( best_hands(["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"]), ["5S 7S 8S 9S 6S"] ) + def test_even_though_an_ace_is_usually_high_a_5_high_straight_flush_is_the_lowest_scoring_straight_flush( + self, + ): + self.assertEqual( + best_hands(["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"]), ["2H 3H 4H 5H 6H"] + ) + if __name__ == "__main__": unittest.main() From caa501c527798c6e4ee79ee6bc474ebfce0c9cab Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 2 Dec 2022 22:43:01 -0800 Subject: [PATCH 171/826] Updates to sgf-parsing. --- .../practice/sgf-parsing/.meta/tests.toml | 54 +++++++++++++++++-- .../practice/sgf-parsing/sgf_parsing_test.py | 33 ++++++++++-- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/exercises/practice/sgf-parsing/.meta/tests.toml b/exercises/practice/sgf-parsing/.meta/tests.toml index c325ae966f..71f5b83a64 100644 --- a/exercises/practice/sgf-parsing/.meta/tests.toml +++ b/exercises/practice/sgf-parsing/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [2668d5dc-109f-4f71-b9d5-8d06b1d6f1cd] description = "empty input" @@ -38,5 +45,46 @@ description = "two child trees" [724eeda6-00db-41b1-8aa9-4d5238ca0130] description = "multiple property values" +[28092c06-275f-4b9f-a6be-95663e69d4db] +description = "within property values, whitespace characters such as tab are converted to spaces" +include = false + +[deaecb9d-b6df-4658-aa92-dcd70f4d472a] +description = "within property values, newli es remain as newlines" +include = false + +[8e4c970e-42d7-440e-bfef-5d7a296868ef] +description = "escaped closing bracket within property value becomes just a closing bracket" +include = false + +[cf371fa8-ba4a-45ec-82fb-38668edcb15f] +description = "escaped backslash in property value becomes just a backslash" +include = false + +[dc13ca67-fac0-4b65-b3fe-c584d6a2c523] +description = "opening bracket within property value doesn't need to be escaped" +include = false + +[a780b97e-8dbb-474e-8f7e-4031902190e8] +description = "semicolon in property value doesn't need to be escaped" + +[0b57a79e-8d89-49e5-82b6-2eaaa6b88ed7] +description = "parentheses in property value don't need to be escaped" + +[c72a33af-9e04-4cc5-9890-1b92262813ac] +description = "escaped tab in property value is converted to space" + +[3a1023d2-7484-4498-8d73-3666bb386e81] +description = "escaped newline in property value is converted to nothing at all" + +[25abf1a4-5205-46f1-8c72-53273b94d009] +description = "escaped t and n in property value are just letters, not whitespace" + +[08e4b8ba-bb07-4431-a3d9-b1f4cdea6dab] +description = "mixing various kinds of whitespace and escaped characters in property value" +reimplements = "11c36323-93fc-495d-bb23-c88ee5844b8c" +include = false + [11c36323-93fc-495d-bb23-c88ee5844b8c] description = "escaped property" +include = false diff --git a/exercises/practice/sgf-parsing/sgf_parsing_test.py b/exercises/practice/sgf-parsing/sgf_parsing_test.py index ff4fac2f75..9b01fb11d0 100644 --- a/exercises/practice/sgf-parsing/sgf_parsing_test.py +++ b/exercises/practice/sgf-parsing/sgf_parsing_test.py @@ -84,7 +84,34 @@ def test_multiple_property_values(self): expected = SgfTree(properties={"A": ["b", "c", "d"]}) self.assertEqual(parse(input_string), expected) - def test_escaped_property(self): - input_string = "(;A[\\]b\nc\nd\t\te \n\\]])" - expected = SgfTree(properties={"A": ["]b\nc\nd e \n]"]}) + def test_semicolon_in_property_value_doesn_t_need_to_be_escaped(self): + input_string = "(;A[a;b][foo]B[bar];C[baz])" + expected = SgfTree( + properties={"A": ["a;b", "foo"], "B": ["bar"]}, + children=[SgfTree({"C": ["baz"]})], + ) + self.assertEqual(parse(input_string), expected) + + def test_parentheses_in_property_value_don_t_need_to_be_escaped(self): + input_string = "(;A[x(y)z][foo]B[bar];C[baz])" + expected = SgfTree( + properties={"A": ["x(y)z", "foo"], "B": ["bar"]}, + children=[SgfTree({"C": ["baz"]})], + ) + self.assertEqual(parse(input_string), expected) + + def test_escaped_tab_in_property_value_is_converted_to_space(self): + input_string = "(;A[hello\\ world])" + expected = SgfTree(properties={"A": ["hello world"]}) + self.assertEqual(parse(input_string), expected) + + def test_escaped_newline_in_property_value_is_converted_to_nothing_at_all(self): + input_string = "(;A[hello\ +world])" + expected = SgfTree(properties={"A": ["helloworld"]}) + self.assertEqual(parse(input_string), expected) + + def test_escaped_t_and_n_in_property_value_are_just_letters_not_whitespace(self): + input_string = "(;A[\t = t and \n = n])" + expected = SgfTree(properties={"A": ["t = t and n = n"]}) self.assertEqual(parse(input_string), expected) From 8c23360c500b0fc491b453b6d5e217b8cea79d12 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 13:58:26 +0100 Subject: [PATCH 172/826] Fixed exemplar --- exercises/practice/sgf-parsing/.meta/example.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/exercises/practice/sgf-parsing/.meta/example.py b/exercises/practice/sgf-parsing/.meta/example.py index cc3327c8ff..f32b20b853 100644 --- a/exercises/practice/sgf-parsing/.meta/example.py +++ b/exercises/practice/sgf-parsing/.meta/example.py @@ -47,7 +47,7 @@ def parse(input_string): root = None current = None stack = list(input_string) - + if input_string == '()': raise ValueError('tree with no nodes') @@ -64,6 +64,10 @@ def pop_until(delimiter): try: value = '' while stack[0] != delimiter: + if stack[0] == "\n": + stack[0] = "n" + if stack[0] == "\t": + stack[0] = "t" value += pop() return value except IndexError as error: From ff2f4b3aabe3332c6cb9dc579e1ad66ea954734f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 4 Dec 2022 23:26:45 -0800 Subject: [PATCH 173/826] Update example.py Deleted unnecessary blank line 50. --- exercises/practice/sgf-parsing/.meta/example.py | 1 - 1 file changed, 1 deletion(-) diff --git a/exercises/practice/sgf-parsing/.meta/example.py b/exercises/practice/sgf-parsing/.meta/example.py index f32b20b853..7711625518 100644 --- a/exercises/practice/sgf-parsing/.meta/example.py +++ b/exercises/practice/sgf-parsing/.meta/example.py @@ -47,7 +47,6 @@ def parse(input_string): root = None current = None stack = list(input_string) - if input_string == '()': raise ValueError('tree with no nodes') From 384e8e219089188c4329776448cc3ab153deb402 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 08:05:14 -0600 Subject: [PATCH 174/826] Create config.json --- .../practice/pig-latin/.approaches/config.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 exercises/practice/pig-latin/.approaches/config.json diff --git a/exercises/practice/pig-latin/.approaches/config.json b/exercises/practice/pig-latin/.approaches/config.json new file mode 100644 index 0000000000..bd808f77fb --- /dev/null +++ b/exercises/practice/pig-latin/.approaches/config.json @@ -0,0 +1,15 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "505e545c-d56c-45e3-8cc6-df3fdb54cc0c", + "slug": "sets-and-slices", + "title": "Sets and slices", + "blurb": "Use sets with slices for parsing.", + "authors": ["bobahop"] + } + ] +} From 75a3a577e127b185afc995085d99f8bf0f512fe1 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 08:35:54 -0600 Subject: [PATCH 175/826] Create introduction.md --- .../pig-latin/.approaches/introduction.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 exercises/practice/pig-latin/.approaches/introduction.md diff --git a/exercises/practice/pig-latin/.approaches/introduction.md b/exercises/practice/pig-latin/.approaches/introduction.md new file mode 100644 index 0000000000..9965022d55 --- /dev/null +++ b/exercises/practice/pig-latin/.approaches/introduction.md @@ -0,0 +1,42 @@ +# Introduction + +There are various ways to solve Pig Latin. +One way is to use [regular expressions][regex] (also known as [regex][regex-ops]) for processing the input. +Solutions using regex can be very succinct, but require familiarity with regex patterns, which are like another language. +Another way is to use a series of conditional statements to test which of several rules the input matches. +Another approach is to use [set][set]s for look-up and then [slice][slicing] the input to return the correct value. + +## General guidance + +At the time of writing only four rules need to be handled, but if they have similar output, they don't need to be handled completely separately. + +```python +VOWELS = {"a", "e", "i", "o", "u"} +VOWELS_Y = {"a", "e", "i", "o", "u", "y"} +SPECIALS = {"xr", "yt"} + + +def translate(text): + piggyfied = [] + + for word in text.split(): + if word[0] in VOWELS or word[0:2] in SPECIALS: + piggyfied.append(word + "ay") + continue + + for pos in range(1, len(word)): + if word[pos] in VOWELS_Y: + pos += 1 if word[pos] == 'u' and word[pos - 1] == "q" else 0 + piggyfied.append(word[pos:] + word[:pos] + "ay") + break + return " ".join(piggyfied) + +``` + +For more information, check the [sets and slices approach][approach-sets-and-slices]. + +[regex]: https://docs.python.org/3/howto/regex.html#regex-howto +[regex-ops]: https://docs.python.org/3/library/re.html?regex +[set]: https://docs.python.org/3/library/stdtypes.html?#set +[slicing]: https://www.learnbyexample.org/python-string-slicing/ +[approach-sets-and-slices]: https://exercism.org/tracks/python/exercises/pig-latin/approaches/sets-and-slices From 75b17840c35aa26e80d3656927435372625b6e66 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 08:40:23 -0600 Subject: [PATCH 176/826] Update introduction.md --- exercises/practice/pig-latin/.approaches/introduction.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/pig-latin/.approaches/introduction.md b/exercises/practice/pig-latin/.approaches/introduction.md index 9965022d55..09cd0ab5d8 100644 --- a/exercises/practice/pig-latin/.approaches/introduction.md +++ b/exercises/practice/pig-latin/.approaches/introduction.md @@ -29,6 +29,7 @@ def translate(text): pos += 1 if word[pos] == 'u' and word[pos - 1] == "q" else 0 piggyfied.append(word[pos:] + word[:pos] + "ay") break + return " ".join(piggyfied) ``` From d66103336cac5ea5e0525b769a15dabd6cde5664 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 08:40:46 -0600 Subject: [PATCH 177/826] Create snippet.txt --- .../pig-latin/.approaches/sets-and-slices/snippet.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 exercises/practice/pig-latin/.approaches/sets-and-slices/snippet.txt diff --git a/exercises/practice/pig-latin/.approaches/sets-and-slices/snippet.txt b/exercises/practice/pig-latin/.approaches/sets-and-slices/snippet.txt new file mode 100644 index 0000000000..e23cee9c2e --- /dev/null +++ b/exercises/practice/pig-latin/.approaches/sets-and-slices/snippet.txt @@ -0,0 +1,7 @@ +for pos in range(1, len(word)): + if word[pos] in VOWELS_Y: + pos += 1 if word[pos] == 'u' and word[pos - 1] == "q" else 0 + piggyfied.append(word[pos:] + word[:pos] + "ay") + break + +return " ".join(piggyfied) From c8525eef7b297d56882df3abeac5159c728e57b5 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 08:43:10 -0600 Subject: [PATCH 178/826] Update introduction.md --- exercises/practice/pig-latin/.approaches/introduction.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/practice/pig-latin/.approaches/introduction.md b/exercises/practice/pig-latin/.approaches/introduction.md index 09cd0ab5d8..0f8807acce 100644 --- a/exercises/practice/pig-latin/.approaches/introduction.md +++ b/exercises/practice/pig-latin/.approaches/introduction.md @@ -10,6 +10,8 @@ Another approach is to use [set][set]s for look-up and then [slice][slicing] the At the time of writing only four rules need to be handled, but if they have similar output, they don't need to be handled completely separately. +## Approach: Sets and slices + ```python VOWELS = {"a", "e", "i", "o", "u"} VOWELS_Y = {"a", "e", "i", "o", "u", "y"} From 219078c9596e931584f5b4e8bae760efed93ec9b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 09:33:22 -0600 Subject: [PATCH 179/826] Create content.md Saving work... --- .../.approaches/sets-and-slices/content.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 exercises/practice/pig-latin/.approaches/sets-and-slices/content.md diff --git a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md new file mode 100644 index 0000000000..88454415cb --- /dev/null +++ b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md @@ -0,0 +1,69 @@ +# Sets and slices + +```python +VOWELS = {"a", "e", "i", "o", "u"} +VOWELS_Y = {"a", "e", "i", "o", "u", "y"} +SPECIALS = {"xr", "yt"} + + +def translate(text): + piggyfied = [] + + for word in text.split(): + if word[0] in VOWELS or word[0:2] in SPECIALS: + piggyfied.append(word + "ay") + continue + + for pos in range(1, len(word)): + if word[pos] in VOWELS_Y: + pos += 1 if word[pos] == 'u' and word[pos - 1] == "q" else 0 + piggyfied.append(word[pos:] + word[:pos] + "ay") + break + return " ".join(piggyfied) + +``` + +This approach begins by defining [sets][set] for looking up matching characters from the input. +Python doesn't _enforce_ having real constant values, +but the sets are defined with all uppercase letters, which is the naming convention for a Python [constant][const]. +It indicates that the value is not intended to be changed. + +The `translate` function begins by defining the list which will hold the parsed value(s). + +The input is [`split()`][split] into a list of its words, which is then iterated. + +[String indexing][string-indexing] is used to check if the first letter of the word is in the set of vowels. +If so, the logical [or][logical-or] operator "short-circuits" and the word plus "ay" is appended to the list. +If the first letter is not a vowel, then a [slice][slicing] of the first two letters is used to check if they match any of the special beginning characters. +If the letters match, the word plus "ay" will be appended to the list. + +If the beginning of the word has not yet matched a rule, then that leaves [ranging][ranging] its characters from position 1 until the [`len()`][len] of the word. + +```exercism/note +When a [range](https://docs.python.org/3/library/stdtypes.html?#range) is provided two arguments, +it generates values from the `start` argument up to but not including the `stop` argument. +This can be referred to as start inclusive, stop exclusive. +``` + Each character is iterated until finding a vowel (at this point, the letter `y` is now considered a vowel.) +If the vowel is `u`, the previous consonant is checked to be `q`. +If so, the position is advanced to be one position after the found `u`. + +A slice is then taken from the position until the end of the word, +plus a slice taken from the beginning of the word and stopped before the position, plus `ay`. +That concatenated string is appended to the list. + +Once all of the words in the input have been iterated, +the [join][join] method is called on a space character to connect all of the words in the list back into a single string. +Since the space is only used as a separator _between_ elements of the list, if the list has only one element, +the space will not be added to the beginning or end of the output string. + +[set]: https://docs.python.org/3/library/stdtypes.html?#set +[const]: https://realpython.com/python-constants/ +[split]: https://docs.python.org/3/library/stdtypes.html?#str.split +[string-indexing]: https://realpython.com/lessons/string-indexing/ +[logical-or]: https://realpython.com/python-or-operator/ +[ranging]: https://www.w3schools.com/python/gloss_python_for_range.asp +[range]: https://docs.python.org/3/library/stdtypes.html?#range +[len]: https://docs.python.org/3/library/functions.html?#len +[slicing]: https://www.learnbyexample.org/python-string-slicing/ +[join]: https://docs.python.org/3/library/stdtypes.html?#str.join From ad2c51f433e15b235b7fc52c5f588d6af7993294 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 10:29:29 -0600 Subject: [PATCH 180/826] Update content.md --- .../.approaches/sets-and-slices/content.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md index 88454415cb..5ee7719700 100644 --- a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md +++ b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md @@ -36,21 +36,26 @@ The input is [`split()`][split] into a list of its words, which is then iterated If so, the logical [or][logical-or] operator "short-circuits" and the word plus "ay" is appended to the list. If the first letter is not a vowel, then a [slice][slicing] of the first two letters is used to check if they match any of the special beginning characters. If the letters match, the word plus "ay" will be appended to the list. +If the beginning of the word matches either condition, the loop [continue][continue]s to the next word. -If the beginning of the word has not yet matched a rule, then that leaves [ranging][ranging] its characters from position 1 until the [`len()`][len] of the word. +If the beginning of the word did not match either condition, +then that leaves [ranging][ranging] its characters from position 1 until the [`len()`][len] of the word. ```exercism/note When a [range](https://docs.python.org/3/library/stdtypes.html?#range) is provided two arguments, -it generates values from the `start` argument up to but not including the `stop` argument. -This can be referred to as start inclusive, stop exclusive. +it generates values from the `start` argument up to _but not including_ the `stop` argument. +This behavior can be referred to as start inclusive, stop exclusive. ``` - Each character is iterated until finding a vowel (at this point, the letter `y` is now considered a vowel.) + +The inner loop iterating characters is nested within the outer loop that iterates the words. +Each character is iterated until finding a vowel (at this point, the letter `y` is now considered a vowel.) If the vowel is `u`, the previous consonant is checked to be `q`. If so, the position is advanced to be one position after the found `u`. A slice is then taken from the position until the end of the word, plus a slice taken from the beginning of the word and stopped before the position, plus `ay`. -That concatenated string is appended to the list. +That concatenated string is appended to the list, [break][break] is called to exit the inner loop, +and execution returns to the outer loop. Once all of the words in the input have been iterated, the [join][join] method is called on a space character to connect all of the words in the list back into a single string. @@ -62,8 +67,10 @@ the space will not be added to the beginning or end of the output string. [split]: https://docs.python.org/3/library/stdtypes.html?#str.split [string-indexing]: https://realpython.com/lessons/string-indexing/ [logical-or]: https://realpython.com/python-or-operator/ +[continue]: https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement [ranging]: https://www.w3schools.com/python/gloss_python_for_range.asp [range]: https://docs.python.org/3/library/stdtypes.html?#range [len]: https://docs.python.org/3/library/functions.html?#len [slicing]: https://www.learnbyexample.org/python-string-slicing/ +[break]: https://docs.python.org/3/reference/simple_stmts.html#the-break-statement [join]: https://docs.python.org/3/library/stdtypes.html?#str.join From 7a0e4346c7166f81bfd51422d09c8b39de06e6ce Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Tue, 29 Nov 2022 10:33:07 -0600 Subject: [PATCH 181/826] Update content.md --- .../practice/pig-latin/.approaches/sets-and-slices/content.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md index 5ee7719700..0f7ab8c28f 100644 --- a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md +++ b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md @@ -19,6 +19,7 @@ def translate(text): pos += 1 if word[pos] == 'u' and word[pos - 1] == "q" else 0 piggyfied.append(word[pos:] + word[:pos] + "ay") break + return " ".join(piggyfied) ``` @@ -53,7 +54,7 @@ If the vowel is `u`, the previous consonant is checked to be `q`. If so, the position is advanced to be one position after the found `u`. A slice is then taken from the position until the end of the word, -plus a slice taken from the beginning of the word and stopped before the position, plus `ay`. +plus a slice taken from the beginning of the word and stopped just before the position, plus `ay`. That concatenated string is appended to the list, [break][break] is called to exit the inner loop, and execution returns to the outer loop. From 38b9e9ec679450f2b68fc9ea71a4c4d9cdff7c0e Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Sat, 3 Dec 2022 08:01:04 -0600 Subject: [PATCH 182/826] Update content.md --- .../practice/pig-latin/.approaches/sets-and-slices/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md index 0f7ab8c28f..54d5a6e809 100644 --- a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md +++ b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md @@ -29,7 +29,7 @@ Python doesn't _enforce_ having real constant values, but the sets are defined with all uppercase letters, which is the naming convention for a Python [constant][const]. It indicates that the value is not intended to be changed. -The `translate` function begins by defining the list which will hold the parsed value(s). +The `translate()` function begins by defining the list which will hold the parsed value(s). The input is [`split()`][split] into a list of its words, which is then iterated. @@ -40,7 +40,7 @@ If the letters match, the word plus "ay" will be appended to the list. If the beginning of the word matches either condition, the loop [continue][continue]s to the next word. If the beginning of the word did not match either condition, -then that leaves [ranging][ranging] its characters from position 1 until the [`len()`][len] of the word. +that leaves [ranging][ranging] its characters from position 1 until the [`len()`][len] of the word. ```exercism/note When a [range](https://docs.python.org/3/library/stdtypes.html?#range) is provided two arguments, From bc9667613470ed9dee41123e178745234e215451 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 5 Dec 2022 18:38:25 +0100 Subject: [PATCH 183/826] Update track_exercises_overview.md --- reference/track_exercises_overview.md | 437 +++++++++++++------------- 1 file changed, 224 insertions(+), 213 deletions(-) diff --git a/reference/track_exercises_overview.md b/reference/track_exercises_overview.md index 383e1e1ebb..8b8fe4f62e 100644 --- a/reference/track_exercises_overview.md +++ b/reference/track_exercises_overview.md @@ -5,180 +5,192 @@
- ## Implemented Practice Exercises
Practice Exercises with Difficulty, Solutions, and Mentor Notes
+| Exercise | Difficulty | Solutions | prereqs | Practices | Mentor Notes | Jinja? | +| ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------ | +| [**Hello World**](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hello-world/solutions?passed_head_tests=true) | NONE | `basics` | | ✅ | +| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | [acronym](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | ✅ | +| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | ✅ | +| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | ✅ | +| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | [allergies](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | ✅ | +| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | ✅ | +| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✅ | +| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✅ | +| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | ✅ | +| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | ❌ | +| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | ✅ | +| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | [binary-search](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | ✅ | +| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | [bob](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | ✅ | +| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✅ | +| [Bottle Song](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bottle-song/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json/#LC1012) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC1012) | | ✅ | +| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | ✅ | +| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/change/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | ✅ | +| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | ✅ | +| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | [clock](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | ✅ | +| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | ✅ | +| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | ✅ | +| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | ✅ | +| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | ✅ | +| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | | ✅ | +| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | ✅ | +| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | ✅ | +| [Diffie Hellman](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diffie-hellman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1956) | NONE | | ✅ | +| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | ✅ | +| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | ✅ | +| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | ❌ | +| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✅ | +| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | ✅ | +| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | ✅ | +| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | ✅ | +| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | | NONE | | ✅ | +| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | ✅ | +| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | [grade-school](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | ✅ | +| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | ✅ | +| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | ✅ | +| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | [hamming](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | ✅ | +| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | ❌ | +| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 🔹 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | [high-scores](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | ✅ | +| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/house/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✅ | +| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✅ | +| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | [isogram](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | ✅ | +| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | [kindergarten-garden](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | ✅ | +| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | ✅ | +| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✅ | +| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | [leap](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | ✅ | +| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1589) | | ❌ | +| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | ❌ | +| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | ✅ | +| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | [luhn](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | ✅ | +| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L401) | [markdown](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | ✅ | +| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | [matching-brackets](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | ✅ | +| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | [matrix](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | ✅ | +| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✅ | +| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | ✅ | +| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | ✅ | +| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | ✅ | +| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | ❌ | +| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | ✅ | +| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | ✅ | +| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✅ | +| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | ✅ | +| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | ✅ | +| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | ✅ | +| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | ✅ | +| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | ✅ | +| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✅ | +| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC661) | [config.json](https://github.com/exercism/python/blob/main/config.json#L661) | | ✅ | +| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | ✅ | +| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | ✅ | +| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | ✅ | +| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | [raindrops](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | ✅ | +| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | ✅ | +| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/react/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | ❌ | +| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | ✅ | +| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC631) | [config.json](https://github.com/exercism/python/blob/main/config.json#L631) | | ✅ | +| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✅ | +| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | ✅ | +| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | ✅ | +| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | [reverse-string](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | ✅ | +| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | ✅ | +| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | ❌ | +| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | ✅ | +| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | ✅ | +| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | ✅ | +| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✅ | +| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✅ | +| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | ✅ | +| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/say/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✅ | +| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | ✅ | +| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | [scrabble-score](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | ✅ | +| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✅ | +| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/series/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✅ | +| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | ✅ | +| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | ✅ | +| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | ✅ | +| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | | ❌ | +| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✅ | +| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✅ | +| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | ✅ | +| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | [sum-of-multiples](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | ✅ | +| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | ✅ | +| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | ✅ | +| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | ❌ | +| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✅ | +| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [twelve-days](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | ✅ | +| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | ✅ | +| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | [two-fer](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | ✅ | +| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | ✅ | +| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [word-count](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | ✅ | +| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | ✅ | +| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | ✅ | +| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | ✅ | +| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | ✅ | +| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | ✅ | + +
-| Exercise | Difficulty | Solutions | Prereqs | Practices | Mentor
Notes | -|--------------------------------------------------------------------------------------------------------------------------------------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| -| [**Hello World**](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hello-world/solutions?passed_head_tests=true) | NONE | `basics` | | -| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | [acronym](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | -| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | -| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | -| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | [allergies](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | -| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | -| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | -| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | -| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | -| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | -| [Beer Song](https://github.com/exercism/python/blob/main/exercises/practice/beer-song/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/beer-song/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/beer-song/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L905) | NONE | | -| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | -| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | [binary-search](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | -| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | [bob](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | -| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | -| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | -| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/change/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | -| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | -| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | [clock](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | -| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | -| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | -| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | -| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | -| [Custom Set](https://github.com/exercism/python/blob/main/exercises/practice/custom-set/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/custom-set/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/custom-set/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1888) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1883) | | -| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | | -| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | -| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | -| [Diffie Hellman](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diffie-hellman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1956) | NONE | | -| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | -| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | -| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | -| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | -| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | -| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | -| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | -| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | | NONE | | -| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | -| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | [grade-school](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | -| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | -| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | -| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | [hamming](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | -| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | -| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | [high-scores](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | -| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/house/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | -| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | -| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | [isogram](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | -| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | [kindergarten-garden](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | -| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | -| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | -| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | [leap](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | -| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1589) | | -| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | -| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | -| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | [luhn](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | -| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L401) | [markdown](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | -| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | [matching-brackets](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | -| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | [matrix](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | -| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | -| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | -| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | -| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | -| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | -| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | -| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | -| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | -| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | -| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | -| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | -| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | -| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | -| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | -| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | -| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | -| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | -| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | [raindrops](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | -| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | -| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/react/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | -| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | -| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | -| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | -| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | -| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | [reverse-string](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | -| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | -| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | -| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | -| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | -| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | -| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | -| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | -| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | -| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/say/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | -| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | -| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | [scrabble-score](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | -| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | -| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/series/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | -| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | -| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | -| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | -| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | | -| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | -| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | -| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | -| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | [sum-of-multiples](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | -| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | -| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | -| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | -| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | -| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [twelve-days](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | -| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | -| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | [two-fer](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | -| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true)| [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | -| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [word-count](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | -| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | -| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | -| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | -| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | -| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | +### Deprecated + +| Exercise | Difficulty | +| -------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| [Accumulate](https://github.com/exercism/python/blob/main/exercises/practice/accumulate/.docs/instructions.md) | 🔹🔹 | +| [Beer Song](https://github.com/exercism/python/blob/main/exercises/practice/beer-song/.docs/instructions.md) | 🔹🔹🔹 | +| [Binary](https://github.com/exercism/python/blob/main/exercises/practice/binary/.docs/instructions.md) | 🔹🔹🔹 | +| [Error Handling](https://github.com/exercism/python/blob/main/exercises/practice/error-handling/.docs/instructions.md) | 🔹🔹🔹 | +| [Hexadecimal](https://github.com/exercism/python/blob/main/exercises/practice/hexadecimal/.docs/instructions.md) | 🔹🔹🔹 | +| [Nucleotide Count](https://github.com/exercism/python/blob/main/exercises/practice/nucleotide-count/.docs/instructions.md) | 🔹🔹 | +| [Parallel Letter Frequency](https://github.com/exercism/python/blob/main/exercises/practice/parallel-letter-frequency/.docs/instructions.md) | 🔹🔹🔹 | +| [Pascal's Triangle](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.md) | 🔹🔹🔹 | +| [Point Mutations](https://github.com/exercism/python/blob/main/exercises/practice/point-mutations/.docs/instructions.md) | 🔹🔹🔹 | +| [Trinary](https://github.com/exercism/python/blob/main/exercises/practice/trinary/.docs/instructions.md) | 🔹🔹🔹🔹 | +| [Custom Set](https://github.com/exercism/python/blob/main/exercises/practice/custom-set/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | -

- ## Concepts Without Planned Exercises
No Exercises Planned
- -| Status | Concept | About&Intro | Exercise | Design Doc or Issue | -|:---------------------------------------------------------------------------------------------------: |------------------------------------------------------------------------------------------------------------------------------ |:---------------------------------------------------------------------------------------------------: |------------------------------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------ | -| ~~ | \*general--Composition | ~~ | NA | NA | -| ~~ | \*general--Data Structures] | ~~ | NA | NA | -| ~~ | \*general--Encapsulation | ~~ | NA | NA | -| ~~ | \*general--Interfaces] | ~~ | NA | NA | -| ~~ | \*general--Lookup efficiency] | ~~ | NA | NA | -| ~~ | \*general--Mutability in Python] | ~~ | NA | NA | -| ~~ | \*general--Mutation | ~~ | NA | NA | -| ~~ | \*general--Polymorphism | ~~ | NA | NA | -| ~~ | \*general--Recursive data structures | ~~ | NA | NA | -| ~~ | \*general--Scope | ~~ | NA | NA | -| ~~ | \*general--Standard Library | ~~ | NA | NA | -| ~~ | \*general--State | ~~ | NA | NA | -| ~~ | \*no stand-alone--del | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--Duck Typing | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--Dynamic Typing | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--Expressions | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--Immutability in Python | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--Operator precedence | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--Operators] | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--Order of Evaluation | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--type | ~~ | Multiple | Multiple | -| ~~ | \*no stand-alone--type conversion | ~~ | Multiple | Multiple | +| Status | Concept | About&Intro | Exercise | Design Doc or Issue | +| :----: | ---------------------------------------- | :---------: | -------- | ------------------- | +| ~~ | \*general--Composition | ~~ | NA | NA | +| ~~ | \*general--Data Structures] | ~~ | NA | NA | +| ~~ | \*general--Encapsulation | ~~ | NA | NA | +| ~~ | \*general--Interfaces] | ~~ | NA | NA | +| ~~ | \*general--Lookup efficiency] | ~~ | NA | NA | +| ~~ | \*general--Mutability in Python] | ~~ | NA | NA | +| ~~ | \*general--Mutation | ~~ | NA | NA | +| ~~ | \*general--Polymorphism | ~~ | NA | NA | +| ~~ | \*general--Recursive data structures | ~~ | NA | NA | +| ~~ | \*general--Scope | ~~ | NA | NA | +| ~~ | \*general--Standard Library | ~~ | NA | NA | +| ~~ | \*general--State | ~~ | NA | NA | +| ~~ | \*no stand-alone--del | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--Duck Typing | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--Dynamic Typing | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--Expressions | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--Immutability in Python | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--Operator precedence | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--Operators] | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--Order of Evaluation | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--type | ~~ | Multiple | Multiple | +| ~~ | \*no stand-alone--type conversion | ~~ | Multiple | Multiple |

- ## Implemented & Planned Concept Exercises -

= live on exercism.org        = drafted but not live

= planned or in progress    @@ -186,73 +198,72 @@
-| Status | Concept | About&Intro | Exercise | Design Doc or Issue |Stub
Docstring Level| -|:----------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------|:---| -| | [basics](https://github.com/exercism/python/blob/main/concepts/basics) | | [Guidos Gorgeous Lasagna](https://github.com/exercism/python/tree/main/tree/main/exercises/concept/guidos-gorgeous-lasagna) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/guidos-gorgeous-lasagna/.meta) | Full | -| | [bools](https://github.com/exercism/python/blob/main/concepts/bools) | | [Ghost Gobble Arcade Game](https://github.com/exercism/python/tree/main/tree/main/exercises/concept/ghost-gobble-arcade-game) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/ghost-gobble-arcade-game/.meta) | Full | -| | [numbers](https://github.com/exercism/python/blob/main/concepts/numbers) | | [Currency Exchange](https://github.com/exercism/python/tree/main/exercises/concept/currency-exchange) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/currency-exchange/.meta) | Full | -| | [complex-numbers](https://github.com/exercism/python/blob/main/concepts/complex-numbers) | | ~ | [#2208](https://github.com/exercism/v3/issues/2208) | TBD | -| | [conditionals](https://github.com/exercism/python/blob/main/concepts/conditionals) | | [Meltdown Mitigation ](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation/.meta) | Full | -| | [comparisons](https://github.com/exercism/python/blob/main/concepts/comparisons) | | [Black Jack](https://github.com/exercism/python/tree/main/exercises/concept/black-jack) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/black-jack/.meta) | Full | -| | [strings](https://github.com/exercism/python/blob/main/concepts/strings) | | [Litte Sister's Vocab](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab/.meta) | Full | -| | [string-methods](https://github.com/exercism/python/blob/main/concepts/string-methods) | | [Litte Sister's Essay](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay/.meta) | Full | -| | [string-formatting](https://github.com/exercism/python/blob/main/concepts/string-formatting) | | [Pretty Leaflet ](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet/.meta) | Full | -| | [lists](https://github.com/exercism/python/blob/main/concepts/lists) | | [Card Games](https://github.com/exercism/python/tree/main/exercises/concept/card-games) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/card-games/.meta) | Full | -| | [list-methods](https://github.com/exercism/python/blob/main/concepts/list-methods) | | [Chaitanas Colossal Coaster](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster/.meta) | Full | -| | [loops](https://github.com/exercism/python/blob/main/concepts/loops) | | [Making the Grade](https://github.com/exercism/python/tree/main/exercises/concept/making-the-grade) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/making-the-grade/.meta) | Full | -| | [tuples](https://github.com/exercism/python/blob/main/concepts/tuples) | | [Tisbury Treasure Hunt](https://github.com/exercism/python/tree/main/exercises/concept/tisbury-treasure-hunt) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/tisbury-treasure-hunt/.meta) | Full | -| | [sequences](https://github.com/exercism/python/blob/main/concepts/sequences) | | ~ | [#2290](https://github.com/exercism/python/issues/2290) | TBD | -| | [dicts](https://github.com/exercism/python/blob/main/concepts/dicts) | | [Inventory Management](https://github.com/exercism/python/tree/main/exercises/concept/inventory-management) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/inventory-management) | Full | -| | [dict-methods](https://github.com/exercism/python/blob/main/concepts/dict-methods) | | ~ | [#2348](https://github.com/exercism/python/issues/2348) | | -| | [sets](https://github.com/exercism/python/blob/main/concepts/sets) | | [Cater Waiter ](https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter/.meta) | Full | -| | [list-comprehensions](https://github.com/exercism/python/blob/main/concepts/list-comprehensions) | | ~ | [#2295](https://github.com/exercism/python/issues/2295) | | -| | [other-comprehensions](https://github.com/exercism/python/blob/main/concepts/other-comprehensions) | | ~ | [#2294](https://github.com/exercism/python/issues/2294) | | -| | [classes](https://github.com/exercism/python/blob/main/concepts/classes) | | [Ellen's Alien Game](https://github.com/exercism/python/tree/main/exercises/concept/ellens-alien-game) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/ellens-alien-game/.meta) | Minimal | -| | [generators](https://github.com/exercism/python/blob/main/concepts/generators) | | Plane Tickets | [PR#2729](https://github.com/exercism/python/pull/2729)/[#2293](https://github.com/exercism/python/issues/2293) | Minimal | -| | [generator-expressions](https://github.com/exercism/python/blob/main/concepts/generator-expressions) | | ~ | [#2292](https://github.com/exercism/python/issues/2292) | | -| | [iterators](https://github.com/exercism/python/blob/main/concepts/iterators) | | ~ | [#2367](https://github.com/exercism/python/issues/2367) | TBD | -| | [functions](https://github.com/exercism/python/blob/main/concepts/functions) | | ~ | [#2353](https://github.com/exercism/python/issues/2353) | | -| | [unpacking-and-multiple-assignment](https://github.com/exercism/python/blob/main/concepts/unpacking-and-multiple-assignment) | | ~ | [#2360](https://github.com/exercism/python/issues/2360) | | -| | [raising-and-handling-errors](https://github.com/exercism/python/blob/main/concepts/raising-and-handling-errors) | | ~ | TBD | | -| | [itertools](https://github.com/exercism/python/blob/main/concepts/itertools) | | ~ | [#2368](https://github.com/exercism/python/issues/2368) | | -| | [with-statement](https://github.com/exercism/python/blob/main/concepts/with-statement) | | ~ | [#2369](https://github.com/exercism/python/issues/2369) | | -| | [enums](https://github.com/exercism/python/blob/main/concepts/enums) | | [Log Levels](https://github.com/exercism/python/tree/main/exercises/concept/log-levels) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/log-levels) | Minimal | -| | [none](https://github.com/exercism/python/blob/main/concepts/none) | | [Restaurant Rozalynn](https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn/.meta) | Minimal | -| | [decorators](https://github.com/exercism/python/blob/main/concepts/decorators) | | ~ | [#2356](https://github.com/exercism/python/issues/2356) | | -| | [rich-comparisons](https://github.com/exercism/python/blob/main/concepts/rich-comparisons) | | ~ | [#2287](https://github.com/exercism/python/issues/2287) | | -| | [function-arguments](https://github.com/exercism/python/blob/main/concepts/function-arguments) | | ~ | [#2354](https://github.com/exercism/python/issues/2354) | | -| | [class-customization](https://github.com/exercism/python/blob/main/concepts/class-customization) | | ~ | [#2350](https://github.com/exercism/python/issues/2350) | | -| | [class-inheritance](https://github.com/exercism/python/blob/main/concepts/class-inheritance) | | ~ | [#2351](https://github.com/exercism/python/issues/2351) | | -| | [user-defined-errors](https://github.com/exercism/python/blob/main/concepts/user-defined-errors) | | ~ | TBD | | -| | [context-manager-customization](https://github.com/exercism/python/blob/main/concepts/context-manager-customization) | | ~ | [#2370](https://github.com/exercism/python/issues/2370) | | -| | [higher-order-functions](https://github.com/exercism/python/blob/main/concepts/higher-order-functions) | | ~ | [#2355](https://github.com/exercism/python/issues/2355) | | -| | [functional-tools](https://github.com/exercism/python/blob/main/concepts/functional-tools) | | ~ | [#2359](https://github.com/exercism/python/issues/2359) | | -| | [functools](https://github.com/exercism/python/blob/main/concepts/functools) | | ~ | [#2366](https://github.com/exercism/python/issues/2366) | | -| | [anonymous-functions](https://github.com/exercism/python/blob/main/concepts) | | ~ | [#2357](https://github.com/exercism/python/issues/2357) | | -| | [descriptors](https://github.com/exercism/python/blob/main/concepts/descriptors) | | ~ | [#2365](https://github.com/exercism/python/issues/2365) | | -| | [aliasing](https://github.com/exercism/python/blob/main/concepts) | | ~ | TBD | | -| | [binary data](https://github.com/exercism/python/blob/main/concepts/binary-data) | | ~ | TBD | | -| | [bitflags](https://github.com/exercism/python/blob/main/concepts/bitflags) | | ~ | TBD | | -| | [bitwise-operators](https://github.com/exercism/python/blob/main/concepts/bitwise-operators) | | ~ | TBD | | -| | [bytes](https://github.com/exercism/python/blob/main/concepts/bytes) | | ~ | TBD | | -| | [class-composition](https://github.com/exercism/python/blob/main/concepts/class-composition) | | ~ | [#2352](https://github.com/exercism/python/issues/2352) | | -| | [class-interfaces](https://github.com/exercism/python/blob/main/concepts/class-interfaces) | | ~ | TBD | | -| | [collections](https://github.com/exercism/python/blob/main/concepts/collections) | | ~ | TBD | | -| | [dataclasses-and-namedtuples](https://github.com/exercism/python/blob/main/concepts/dataclasses-and-namedtuples) | | ~ | [#2361](https://github.com/exercism/python/issues/2361) | | -| | [import](https://github.com/exercism/python/blob/main/concepts/import) | | ~ | ON HOLD | | -| | [memoryview](https://github.com/exercism/python/blob/main/concepts/memoryview) | | ~ | TBD | | -| | [operator-overloading](https://github.com/exercism/python/blob/main/concepts/operator-overloading) | | ~ | TBD | | -| | [regular-expressions](https://github.com/exercism/python/blob/main/concepts/regular-expressions) | | ~ | TBD | | -| | [string-methods-splitting](https://github.com/exercism/python/blob/main/concepts/string-methods-splitting) | | ~ | TBD | | -| | [testing](https://github.com/exercism/python/blob/main/concepts/testing) | | ~ | TBD | | -| | [text-processing](https://github.com/exercism/python/blob/main/concepts/text-processing) | | ~ | TBD | | -| | [type-hinting](https://github.com/exercism/python/blob/main/concepts/type-hinting) | | ~ | TBD | | -| | [unicode-regular-expressions](https://github.com/exercism/python/blob/main/concepts/unicode-regular-expressions) | | ~ | TBD | | -| | [walrus-operator](https://github.com/exercism/python/blob/main/concepts/walrus-operator) | | ~ | TBD | | +| Status | Concept | About&Intro | Exercise | Design Doc or Issue | Stub
Docstring Level | +| :--------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | :---------------------- | +| | [basics](https://github.com/exercism/python/blob/main/concepts/basics) | | [Guidos Gorgeous Lasagna](https://github.com/exercism/python/tree/main/tree/main/exercises/concept/guidos-gorgeous-lasagna) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/guidos-gorgeous-lasagna/.meta) | Full | +| | [bools](https://github.com/exercism/python/blob/main/concepts/bools) | | [Ghost Gobble Arcade Game](https://github.com/exercism/python/tree/main/tree/main/exercises/concept/ghost-gobble-arcade-game) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/ghost-gobble-arcade-game/.meta) | Full | +| | [numbers](https://github.com/exercism/python/blob/main/concepts/numbers) | | [Currency Exchange](https://github.com/exercism/python/tree/main/exercises/concept/currency-exchange) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/currency-exchange/.meta) | Full | +| | [complex-numbers](https://github.com/exercism/python/blob/main/concepts/complex-numbers) | | ~ | [#2208](https://github.com/exercism/v3/issues/2208) | TBD | +| | [conditionals](https://github.com/exercism/python/blob/main/concepts/conditionals) | | [Meltdown Mitigation ](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation/.meta) | Full | +| | [comparisons](https://github.com/exercism/python/blob/main/concepts/comparisons) | | [Black Jack](https://github.com/exercism/python/tree/main/exercises/concept/black-jack) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/black-jack/.meta) | Full | +| | [strings](https://github.com/exercism/python/blob/main/concepts/strings) | | [Litte Sister's Vocab](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab/.meta) | Full | +| | [string-methods](https://github.com/exercism/python/blob/main/concepts/string-methods) | | [Litte Sister's Essay](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay/.meta) | Full | +| | [string-formatting](https://github.com/exercism/python/blob/main/concepts/string-formatting) | | [Pretty Leaflet ](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet/.meta) | Full | +| | [lists](https://github.com/exercism/python/blob/main/concepts/lists) | | [Card Games](https://github.com/exercism/python/tree/main/exercises/concept/card-games) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/card-games/.meta) | Full | +| | [list-methods](https://github.com/exercism/python/blob/main/concepts/list-methods) | | [Chaitanas Colossal Coaster](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster/.meta) | Full | +| | [loops](https://github.com/exercism/python/blob/main/concepts/loops) | | [Making the Grade](https://github.com/exercism/python/tree/main/exercises/concept/making-the-grade) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/making-the-grade/.meta) | Full | +| | [tuples](https://github.com/exercism/python/blob/main/concepts/tuples) | | [Tisbury Treasure Hunt](https://github.com/exercism/python/tree/main/exercises/concept/tisbury-treasure-hunt) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/tisbury-treasure-hunt/.meta) | Full | +| | [sequences](https://github.com/exercism/python/blob/main/concepts/sequences) | | ~ | [#2290](https://github.com/exercism/python/issues/2290) | TBD | +| | [dicts](https://github.com/exercism/python/blob/main/concepts/dicts) | | [Inventory Management](https://github.com/exercism/python/tree/main/exercises/concept/inventory-management) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/inventory-management) | Full | +| | [dict-methods](https://github.com/exercism/python/blob/main/concepts/dict-methods) | | ~ | [#2348](https://github.com/exercism/python/issues/2348) | | +| | [sets](https://github.com/exercism/python/blob/main/concepts/sets) | | [Cater Waiter ](https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter/.meta) | Full | +| | [list-comprehensions](https://github.com/exercism/python/blob/main/concepts/list-comprehensions) | | ~ | [#2295](https://github.com/exercism/python/issues/2295) | | +| | [other-comprehensions](https://github.com/exercism/python/blob/main/concepts/other-comprehensions) | | ~ | [#2294](https://github.com/exercism/python/issues/2294) | | +| | [classes](https://github.com/exercism/python/blob/main/concepts/classes) | | [Ellen's Alien Game](https://github.com/exercism/python/tree/main/exercises/concept/ellens-alien-game) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/ellens-alien-game/.meta) | Minimal | +| | [generators](https://github.com/exercism/python/blob/main/concepts/generators) | | Plane Tickets | [PR#2729](https://github.com/exercism/python/pull/2729)/[#2293](https://github.com/exercism/python/issues/2293) | Minimal | +| | [generator-expressions](https://github.com/exercism/python/blob/main/concepts/generator-expressions) | | ~ | [#2292](https://github.com/exercism/python/issues/2292) | | +| | [iterators](https://github.com/exercism/python/blob/main/concepts/iterators) | | ~ | [#2367](https://github.com/exercism/python/issues/2367) | TBD | +| | [functions](https://github.com/exercism/python/blob/main/concepts/functions) | | ~ | [#2353](https://github.com/exercism/python/issues/2353) | | +| | [unpacking-and-multiple-assignment](https://github.com/exercism/python/blob/main/concepts/unpacking-and-multiple-assignment) | | ~ | [#2360](https://github.com/exercism/python/issues/2360) | | +| | [raising-and-handling-errors](https://github.com/exercism/python/blob/main/concepts/raising-and-handling-errors) | | ~ | TBD | | +| | [itertools](https://github.com/exercism/python/blob/main/concepts/itertools) | | ~ | [#2368](https://github.com/exercism/python/issues/2368) | | +| | [with-statement](https://github.com/exercism/python/blob/main/concepts/with-statement) | | ~ | [#2369](https://github.com/exercism/python/issues/2369) | | +| | [enums](https://github.com/exercism/python/blob/main/concepts/enums) | | [Log Levels](https://github.com/exercism/python/tree/main/exercises/concept/log-levels) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/log-levels) | Minimal | +| | [none](https://github.com/exercism/python/blob/main/concepts/none) | | [Restaurant Rozalynn](https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn/.meta) | Minimal | +| | [decorators](https://github.com/exercism/python/blob/main/concepts/decorators) | | ~ | [#2356](https://github.com/exercism/python/issues/2356) | | +| | [rich-comparisons](https://github.com/exercism/python/blob/main/concepts/rich-comparisons) | | ~ | [#2287](https://github.com/exercism/python/issues/2287) | | +| | [function-arguments](https://github.com/exercism/python/blob/main/concepts/function-arguments) | | ~ | [#2354](https://github.com/exercism/python/issues/2354) | | +| | [class-customization](https://github.com/exercism/python/blob/main/concepts/class-customization) | | ~ | [#2350](https://github.com/exercism/python/issues/2350) | | +| | [class-inheritance](https://github.com/exercism/python/blob/main/concepts/class-inheritance) | | ~ | [#2351](https://github.com/exercism/python/issues/2351) | | +| | [user-defined-errors](https://github.com/exercism/python/blob/main/concepts/user-defined-errors) | | ~ | TBD | | +| | [context-manager-customization](https://github.com/exercism/python/blob/main/concepts/context-manager-customization) | | ~ | [#2370](https://github.com/exercism/python/issues/2370) | | +| | [higher-order-functions](https://github.com/exercism/python/blob/main/concepts/higher-order-functions) | | ~ | [#2355](https://github.com/exercism/python/issues/2355) | | +| | [functional-tools](https://github.com/exercism/python/blob/main/concepts/functional-tools) | | ~ | [#2359](https://github.com/exercism/python/issues/2359) | | +| | [functools](https://github.com/exercism/python/blob/main/concepts/functools) | | ~ | [#2366](https://github.com/exercism/python/issues/2366) | | +| | [anonymous-functions](https://github.com/exercism/python/blob/main/concepts) | | ~ | [#2357](https://github.com/exercism/python/issues/2357) | | +| | [descriptors](https://github.com/exercism/python/blob/main/concepts/descriptors) | | ~ | [#2365](https://github.com/exercism/python/issues/2365) | | +| | [aliasing](https://github.com/exercism/python/blob/main/concepts) | | ~ | TBD | | +| | [binary data](https://github.com/exercism/python/blob/main/concepts/binary-data) | | ~ | TBD | | +| | [bitflags](https://github.com/exercism/python/blob/main/concepts/bitflags) | | ~ | TBD | | +| | [bitwise-operators](https://github.com/exercism/python/blob/main/concepts/bitwise-operators) | | ~ | TBD | | +| | [bytes](https://github.com/exercism/python/blob/main/concepts/bytes) | | ~ | TBD | | +| | [class-composition](https://github.com/exercism/python/blob/main/concepts/class-composition) | | ~ | [#2352](https://github.com/exercism/python/issues/2352) | | +| | [class-interfaces](https://github.com/exercism/python/blob/main/concepts/class-interfaces) | | ~ | TBD | | +| | [collections](https://github.com/exercism/python/blob/main/concepts/collections) | | ~ | TBD | | +| | [dataclasses-and-namedtuples](https://github.com/exercism/python/blob/main/concepts/dataclasses-and-namedtuples) | | ~ | [#2361](https://github.com/exercism/python/issues/2361) | | +| | [import](https://github.com/exercism/python/blob/main/concepts/import) | | ~ | ON HOLD | | +| | [memoryview](https://github.com/exercism/python/blob/main/concepts/memoryview) | | ~ | TBD | | +| | [operator-overloading](https://github.com/exercism/python/blob/main/concepts/operator-overloading) | | ~ | TBD | | +| | [regular-expressions](https://github.com/exercism/python/blob/main/concepts/regular-expressions) | | ~ | TBD | | +| | [string-methods-splitting](https://github.com/exercism/python/blob/main/concepts/string-methods-splitting) | | ~ | TBD | | +| | [testing](https://github.com/exercism/python/blob/main/concepts/testing) | | ~ | TBD | | +| | [text-processing](https://github.com/exercism/python/blob/main/concepts/text-processing) | | ~ | TBD | | +| | [type-hinting](https://github.com/exercism/python/blob/main/concepts/type-hinting) | | ~ | TBD | | +| | [unicode-regular-expressions](https://github.com/exercism/python/blob/main/concepts/unicode-regular-expressions) | | ~ | TBD | | +| | [walrus-operator](https://github.com/exercism/python/blob/main/concepts/walrus-operator) | | ~ | TBD | |

- ## Concept Exercise Tree ```mermaid From 1168afc639646525e3214fdf92233320250b9221 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 5 Dec 2022 19:37:16 +0100 Subject: [PATCH 184/826] Update track_exercises_overview.md --- reference/track_exercises_overview.md | 240 +++++++++++++------------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/reference/track_exercises_overview.md b/reference/track_exercises_overview.md index 8b8fe4f62e..f7a0416803 100644 --- a/reference/track_exercises_overview.md +++ b/reference/track_exercises_overview.md @@ -11,126 +11,126 @@

Practice Exercises with Difficulty, Solutions, and Mentor Notes
-| Exercise | Difficulty | Solutions | prereqs | Practices | Mentor Notes | Jinja? | -| ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------ | -| [**Hello World**](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hello-world/solutions?passed_head_tests=true) | NONE | `basics` | | ✅ | -| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | [acronym](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | ✅ | -| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | ✅ | -| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | ✅ | -| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | [allergies](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | ✅ | -| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | ✅ | -| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✅ | -| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✅ | -| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | ✅ | -| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | ❌ | -| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | ✅ | -| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | [binary-search](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | ✅ | -| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | [bob](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | ✅ | -| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✅ | -| [Bottle Song](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bottle-song/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json/#LC1012) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC1012) | | ✅ | -| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | ✅ | -| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/change/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | ✅ | -| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | ✅ | -| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | [clock](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | ✅ | -| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | ✅ | -| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | ✅ | -| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | ✅ | -| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | ✅ | -| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | | ✅ | -| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | ✅ | -| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | ✅ | -| [Diffie Hellman](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diffie-hellman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1956) | NONE | | ✅ | -| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | ✅ | -| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | ✅ | -| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | ❌ | -| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✅ | -| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | ✅ | -| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | ✅ | -| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | ✅ | -| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | | NONE | | ✅ | -| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | ✅ | -| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | [grade-school](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | ✅ | -| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | ✅ | -| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | ✅ | -| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | [hamming](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | ✅ | -| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | ❌ | -| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 🔹 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | [high-scores](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | ✅ | -| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/house/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✅ | -| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✅ | -| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | [isogram](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | ✅ | -| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | [kindergarten-garden](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | ✅ | -| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | ✅ | -| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✅ | -| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | [leap](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | ✅ | -| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1589) | | ❌ | -| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | ❌ | -| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | ✅ | -| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | [luhn](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | ✅ | -| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L401) | [markdown](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | ✅ | -| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | [matching-brackets](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | ✅ | -| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | [matrix](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | ✅ | -| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✅ | -| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | ✅ | -| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | ✅ | -| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | ✅ | -| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | ❌ | -| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | ✅ | -| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | ✅ | -| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✅ | -| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | ✅ | -| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | ✅ | -| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | ✅ | -| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | ✅ | -| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | ✅ | -| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✅ | -| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC661) | [config.json](https://github.com/exercism/python/blob/main/config.json#L661) | | ✅ | -| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | ✅ | -| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | ✅ | -| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | ✅ | -| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | [raindrops](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | ✅ | -| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | ✅ | -| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/react/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | ❌ | -| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | ✅ | -| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC631) | [config.json](https://github.com/exercism/python/blob/main/config.json#L631) | | ✅ | -| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✅ | -| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | ✅ | -| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | ✅ | -| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | [reverse-string](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | ✅ | -| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | ✅ | -| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | ❌ | -| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | ✅ | -| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | ✅ | -| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | ✅ | -| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✅ | -| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✅ | -| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | ✅ | -| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/say/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✅ | -| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | ✅ | -| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | [scrabble-score](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | ✅ | -| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✅ | -| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/series/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✅ | -| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | ✅ | -| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | ✅ | -| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | ✅ | -| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | | ❌ | -| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✅ | -| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✅ | -| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | ✅ | -| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | [sum-of-multiples](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | ✅ | -| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | ✅ | -| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | ✅ | -| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | ❌ | -| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✅ | -| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [twelve-days](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | ✅ | -| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | ✅ | -| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | [two-fer](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | ✅ | -| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | ✅ | -| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [word-count](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | ✅ | -| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | ✅ | -| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | ✅ | -| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | ✅ | -| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | ✅ | -| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | ✅ | +| Exercise | Difficulty | Solutions | Prereqs | Practices | Mentor Notes | Jinja?      | Approaches? | +| ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------- | +| [**Hello World**](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hello-world/solutions?passed_head_tests=true) | NONE | `basics` | | ✅ | ❌ | +| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | [acronym](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | ✅ | ❌ | +| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | ✅ | ❌ | +| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | ✅ | ❌ | +| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | [allergies](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | ✅ | ❌ | +| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | ✅ | ❌ | +| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✅ | ❌ | +| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✅ | ❌ | +| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | ✅ | ❌ | +| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | ❌ | ❌ | +| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | ✅ | ❌ | +| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | [binary-search](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | ✅ | ❌ | +| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | [bob](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | ✅ | ✅ | +| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✅ | ❌ | +| [Bottle Song](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bottle-song/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json/#LC1012) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC1012) | | ✅ | ❌ | +| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | ✅ | ❌ | +| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/change/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | ✅ | ❌ | +| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | ✅ | ❌ | +| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | [clock](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | ✅ | ❌ | +| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | ✅ | ❌ | +| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | ✅ | ❌ | +| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | ✅ | ❌ | +| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | ✅ | ❌ | +| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | | ✅ | ❌ | +| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | ✅ | ❌ | +| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | ✅ | ❌ | +| [Diffie Hellman](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diffie-hellman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1956) | NONE | | ✅ | ❌ | +| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | ✅ | ❌ | +| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | ✅ | ❌ | +| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | ❌ | ❌ | +| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✅ | ❌ | +| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | ✅ | ❌ | +| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | ✅ | ❌ | +| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | ✅ | ❌ | +| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | | NONE | | ✅ | ❌ | +| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | ✅ | ❌ | +| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | [grade-school](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | ✅ | ❌ | +| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | ✅ | ✅ | +| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | ✅ | ❌ | +| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | [hamming](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | ✅ | ❌ | +| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | ❌ | ❌ | +| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 🔹 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | [high-scores](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | ✅ | ❌ | +| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/house/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✅ | ❌ | +| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✅ | ❌ | +| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | [isogram](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | ✅ | ✅ | +| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | [kindergarten-garden](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | ✅ | ❌ | +| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | ✅ | ❌ | +| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✅ | ❌ | +| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | [leap](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | ✅ | ✅ | +| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1589) | | ❌ | ❌ | +| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | ❌ | ❌ | +| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | ✅ | ❌ | +| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | [luhn](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | ✅ | ❌ | +| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L401) | [markdown](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | ✅ | ❌ | +| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | [matching-brackets](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | ✅ | ❌ | +| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | [matrix](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | ✅ | ❌ | +| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✅ | ❌ | +| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | ✅ | ❌ | +| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | ✅ | ❌ | +| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | ✅ | ❌ | +| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | ❌ | ❌ | +| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | ✅ | ❌ | +| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | ✅ | ✅ | +| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✅ | ❌ | +| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | ✅ | ❌ | +| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | ✅ | ✅ | +| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | ✅ | ❌ | +| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | ✅ | ❌ | +| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | ✅ | ❌ | +| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✅ | ❌ | +| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC661) | [config.json](https://github.com/exercism/python/blob/main/config.json#L661) | | ✅ | ❌ | +| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | ✅ | ❌ | +| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | ✅ | ❌ | +| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | ✅ | ❌ | +| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | [raindrops](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | ✅ | ❌ | +| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | ✅ | ❌ | +| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/react/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | ❌ | ❌ | +| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | ✅ | ❌ | +| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC631) | [config.json](https://github.com/exercism/python/blob/main/config.json#L631) | | ✅ | ❌ | +| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✅ | ❌ | +| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | ✅ | ❌ | +| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | ✅ | ❌ | +| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | [reverse-string](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | ✅ | ❌ | +| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | ✅ | ✅ | +| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | ❌ | ❌ | +| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | ✅ | ❌ | +| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | ✅ | ❌ | +| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | ✅ | ❌ | +| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✅ | ❌ | +| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✅ | ❌ | +| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | ✅ | ❌ | +| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/say/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✅ | ❌ | +| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | ✅ | ❌ | +| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | [scrabble-score](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | ✅ | ❌ | +| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✅ | ❌ | +| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/series/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✅ | ❌ | +| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | ✅ | ❌ | +| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | ✅ | ❌ | +| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | ✅ | ❌ | +| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | | ❌ | ❌ | +| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✅ | ❌ | +| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✅ | ❌ | +| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | ✅ | ❌ | +| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | [sum-of-multiples](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | ✅ | ❌ | +| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | ✅ | ❌ | +| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | ✅ | ❌ | +| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | ❌ | ❌ | +| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✅ | ❌ | +| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [twelve-days](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | ✅ | ❌ | +| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | ✅ | ❌ | +| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | [two-fer](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | ✅ | ❌ | +| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | ✅ | ❌ | +| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [word-count](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | ✅ | ❌ | +| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | ✅ | ❌ | +| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | ✅ | ✅ | +| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | ✅ | ❌ | +| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | ✅ | ❌ | +| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | ✅ | ❌ |
From eb69066cde65f84b8b18055023fd87f53ac47c7c Mon Sep 17 00:00:00 2001 From: Meatball Date: Mon, 5 Dec 2022 20:51:36 +0100 Subject: [PATCH 185/826] Added killer-sudoku-helper --- config.json | 16 +++++ .../.docs/instructions.md | 63 +++++++++++++++++++ .../killer-sudoku-helper/.meta/config.json | 21 +++++++ .../killer-sudoku-helper/.meta/example.py | 10 +++ .../killer-sudoku-helper/.meta/template.j2 | 19 ++++++ .../killer-sudoku-helper/.meta/tests.toml | 49 +++++++++++++++ .../killer_sudoku_helper.py | 13 ++++ .../killer_sudoku_helper_test.py | 48 ++++++++++++++ 8 files changed, 239 insertions(+) create mode 100644 exercises/practice/killer-sudoku-helper/.docs/instructions.md create mode 100644 exercises/practice/killer-sudoku-helper/.meta/config.json create mode 100644 exercises/practice/killer-sudoku-helper/.meta/example.py create mode 100644 exercises/practice/killer-sudoku-helper/.meta/template.j2 create mode 100644 exercises/practice/killer-sudoku-helper/.meta/tests.toml create mode 100644 exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py create mode 100644 exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py diff --git a/config.json b/config.json index b2a3c8ebc3..748c14b105 100644 --- a/config.json +++ b/config.json @@ -1005,6 +1005,22 @@ ], "difficulty": 3 }, + { + "slug": "killer-sudoku-helper", + "name": "Killer Sudoku Helper", + "uuid": "7b16fc93-791b-42a9-8aae-1f78fef2f2f3", + "practices": ["list-comprehensions"], + "prerequisites": [ + "conditionals", + "lists", + "list-methods", + "loops", + "numbers", + "strings", + "string-methods" + ], + "difficulty": 3 + }, { "slug": "bottle-song", "name": "Bottle Song", diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md new file mode 100644 index 0000000000..93469a09cc --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -0,0 +1,63 @@ +# Instructions + +A friend of yours is learning how to solve Killer Sudokus (rules below) but struggling to figure out which digits can go in a cage. +They ask you to help them out by writing a small program that lists all valid combinations for a given cage, and any constraints that affect the cage. + +To make the output of your program easy to read, the combinations it returns must be sorted. + +## Killer Sudoku Rules + +- [Standard Sudoku rules][sudoku-rules] apply. +- The digits in a cage, usually marked by a dotted line, add up to the small number given in the corner of the cage. +- A digit may only occur once in a cage. + +For a more detailed explanation, check out [this guide][killer-guide]. + +## Example 1: Cage with only 1 possible combination + +In a 3-digit cage with a sum of 7, there is only one valid combination: 124. + +- 1 + 2 + 4 = 7 +- Any other combination that adds up to 7, e.g. 232, would violate the rule of not repeating digits within a cage. + +![Sudoku grid, with three killer cages that are marked as grouped together. The first killer cage is in the 3×3 box in the top left corner of the grid. The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 5. The numbers are highlighted in red to indicate a mistake. The second killer cage is in the central 3×3 box of the grid. The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 4. None of the numbers in this cage are highlighted and therefore don't contain any mistakes. The third killer cage follows the outside corner of the central 3×3 box of the grid. It is made up of the following three cells: the top left cell of the cage contains a 2, highlighted in red, and a cage sum of 7. The top right cell of the cage contains a 3. The bottom right cell of the cage contains a 2, highlighted in red. All other cells are empty.][one-solution-img] + +## Example 2: Cage with several combinations + +In a 2-digit cage with a sum 10, there are 4 possible combinations: + +- 19 +- 28 +- 37 +- 46 + +![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. Each continguous two rows form a killer cage and are marked as grouped together. From top to bottom: first group is a cell with value 1 and a pencil mark indicating a cage sum of 10, cell with value 9. Second group is a cell with value 2 and a pencil mark of 10, cell with value 8. Third group is a cell with value 3 and a pencil mark of 10, cell with value 7. Fourth group is a cell with value 4 and a pencil mark of 10, cell with value 6. The last cell in the column is empty.][four-solutions-img] + +## Example 3: Cage with several combinations that is restricted + +In a 2-digit cage with a sum 10, where the column already contains a 1 and a 4, there are 2 possible combinations: + +- 28 +- 37 + +19 and 46 are not possible due to the 1 and 4 in the column according to standard Sudoku rules. + +![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. The first row contains a 4, the second is empty, and the third contains a 1. The 1 is highlighted in red to indicate a mistake. The last 6 rows in the column form killer cages of two cells each. From top to bottom: first group is a cell with value 2 and a pencil mark indicating a cage sum of 10, cell with value 8. Second group is a cell with value 3 and a pencil mark of 10, cell with value 7. Third group is a cell with value 1, highlighted in red, and a pencil mark of 10, cell with value 9.][not-possible-img] + +## Trying it yourself + +If you want to give an approachable Killer Sudoku a go, you can try out [this puzzle][clover-puzzle] by Clover, featured by [Mark Goodliffe on Cracking The Cryptic on the 21st of June 2021][goodliffe-video]. + +You can also find Killer Sudokus in varying difficulty in numerous newspapers, as well as Sudoku apps, books and websites. + +## Credit + +The screenshots above have been generated using [F-Puzzles.com](https://www.f-puzzles.com/), a Puzzle Setting Tool by Eric Fox. + +[sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/ +[killer-guide]: https://masteringsudoku.com/killer-sudoku/ +[one-solution-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example1.png +[four-solutions-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example2.png +[not-possible-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example3.png +[clover-puzzle]: https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R +[goodliffe-video]: https://youtu.be/c_NjEbFEeW0?t=1180 diff --git a/exercises/practice/killer-sudoku-helper/.meta/config.json b/exercises/practice/killer-sudoku-helper/.meta/config.json new file mode 100644 index 0000000000..eb5a6f84bf --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "meatball133", + "Bethanyg" + ], + "contributors": [], + "files": { + "solution": [ + "killer_sudoku_helper.py" + ], + "test": [ + "killer_sudoku_helper_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Write a tool that makes it easier to solve Killer Sudokus", + "source": "Created by Sascha Mann, Jeremy Walker, and BethanyG for the Julia track on Exercism.", + "source_url": "https://github.com/exercism/julia/pull/413" +} diff --git a/exercises/practice/killer-sudoku-helper/.meta/example.py b/exercises/practice/killer-sudoku-helper/.meta/example.py new file mode 100644 index 0000000000..ba42d25f7d --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/example.py @@ -0,0 +1,10 @@ +import itertools + +def combinations(target, size, exclude): + possible = [i for i in range(1, target) if i not in exclude] + result = [seq for i in range(len(possible), 0, -1) + for seq in itertools.combinations(possible, i) + if sum(seq) == target] + + + return result diff --git a/exercises/practice/killer-sudoku-helper/.meta/template.j2 b/exercises/practice/killer-sudoku-helper/.meta/template.j2 new file mode 100644 index 0000000000..c107933784 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/template.j2 @@ -0,0 +1,19 @@ +{%- import "generator_macros.j2" as macros with context -%} +{% macro test_case(case) -%} + {%- set input = case["input"] -%} + def test_{{ case["description"] | to_snake }}(self): + self.assertEqual({{ case["property"] | to_snake }}( + {{ case["input"]["cage"]["sum"] }}, + {{ case["input"]["cage"]["size"] }}, + {{ case["input"]["cage"]["exclude"] }}), + {{ case["expected"] }}) +{%- endmacro %} +{{ macros.header()}} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases[0]["cases"] -%} + {{ test_case(case) }} + {% endfor %} + {% for case in cases[1:] -%} + {{ test_case(case) }} + {% endfor %} diff --git a/exercises/practice/killer-sudoku-helper/.meta/tests.toml b/exercises/practice/killer-sudoku-helper/.meta/tests.toml new file mode 100644 index 0000000000..19c23e8a92 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/tests.toml @@ -0,0 +1,49 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[2aaa8f13-11b5-4054-b95c-a906e4d79fb6] +description = "Trivial 1-digit cages -> 1" + +[4645da19-9fdd-4087-a910-a6ed66823563] +description = "Trivial 1-digit cages -> 2" + +[07cfc704-f8aa-41b2-8f9a-cbefb674cb48] +description = "Trivial 1-digit cages -> 3" + +[22b8b2ba-c4fd-40b3-b1bf-40aa5e7b5f24] +description = "Trivial 1-digit cages -> 4" + +[b75d16e2-ff9b-464d-8578-71f73094cea7] +description = "Trivial 1-digit cages -> 5" + +[bcbf5afc-4c89-4ff6-9357-07ab4d42788f] +description = "Trivial 1-digit cages -> 6" + +[511b3bf8-186f-4e35-844f-c804d86f4a7a] +description = "Trivial 1-digit cages -> 7" + +[bd09a60d-3aca-43bd-b6aa-6ccad01bedda] +description = "Trivial 1-digit cages -> 8" + +[9b539f27-44ea-4ff8-bd3d-c7e136bee677] +description = "Trivial 1-digit cages -> 9" + +[0a8b2078-b3a4-4dbd-be0d-b180f503d5c3] +description = "Cage with sum 45 contains all digits 1:9" + +[2635d7c9-c716-4da1-84f1-c96e03900142] +description = "Cage with only 1 possible combination" + +[a5bde743-e3a2-4a0c-8aac-e64fceea4228] +description = "Cage with several combinations" + +[dfbf411c-737d-465a-a873-ca556360c274] +description = "Cage with several combinations that is restricted" diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py new file mode 100644 index 0000000000..f5103b776e --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py @@ -0,0 +1,13 @@ +import itertools + +def combinations(target, size, exclude): + result = [] + possible = [i for i in range(1, target) if i not in exclude] + if size == 1: + return [[target]] + else: + for i in range(len(possible), 0, -1): + for seq in itertools.combinations(possible, i): + if sum(seq) == target and len(seq) == size: + result.append(list(seq)) + return result diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py new file mode 100644 index 0000000000..b573205465 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py @@ -0,0 +1,48 @@ +import unittest + +from killer_sudoku_helper import ( + combinations, +) + +# Tests adapted from `problem-specifications//canonical-data.json` + + +class KillerSudokuHelperTest(unittest.TestCase): + def test_1(self): + self.assertEqual(combinations(1, 1, []), [[1]]) + + def test_2(self): + self.assertEqual(combinations(2, 1, []), [[2]]) + + def test_3(self): + self.assertEqual(combinations(3, 1, []), [[3]]) + + def test_4(self): + self.assertEqual(combinations(4, 1, []), [[4]]) + + def test_5(self): + self.assertEqual(combinations(5, 1, []), [[5]]) + + def test_6(self): + self.assertEqual(combinations(6, 1, []), [[6]]) + + def test_7(self): + self.assertEqual(combinations(7, 1, []), [[7]]) + + def test_8(self): + self.assertEqual(combinations(8, 1, []), [[8]]) + + def test_9(self): + self.assertEqual(combinations(9, 1, []), [[9]]) + + def test_cage_with_sum_45_contains_all_digits_1_9(self): + self.assertEqual(combinations(45, 9, []), [[1, 2, 3, 4, 5, 6, 7, 8, 9]]) + + def test_cage_with_only_1_possible_combination(self): + self.assertEqual(combinations(7, 3, []), [[1, 2, 4]]) + + def test_cage_with_several_combinations(self): + self.assertEqual(combinations(10, 2, []), [[1, 9], [2, 8], [3, 7], [4, 6]]) + + def test_cage_with_several_combinations_that_is_restricted(self): + self.assertEqual(combinations(10, 2, [1, 4]), [[2, 8], [3, 7]]) From 14545d736f29c204fe858850d8ebaec7e0857061 Mon Sep 17 00:00:00 2001 From: Meatball Date: Mon, 5 Dec 2022 20:54:19 +0100 Subject: [PATCH 186/826] fixes --- .../practice/killer-sudoku-helper/.meta/example.py | 13 ++++++++----- .../killer-sudoku-helper/killer_sudoku_helper.py | 13 +------------ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/exercises/practice/killer-sudoku-helper/.meta/example.py b/exercises/practice/killer-sudoku-helper/.meta/example.py index ba42d25f7d..f5103b776e 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/example.py +++ b/exercises/practice/killer-sudoku-helper/.meta/example.py @@ -1,10 +1,13 @@ import itertools def combinations(target, size, exclude): + result = [] possible = [i for i in range(1, target) if i not in exclude] - result = [seq for i in range(len(possible), 0, -1) - for seq in itertools.combinations(possible, i) - if sum(seq) == target] - - + if size == 1: + return [[target]] + else: + for i in range(len(possible), 0, -1): + for seq in itertools.combinations(possible, i): + if sum(seq) == target and len(seq) == size: + result.append(list(seq)) return result diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py index f5103b776e..03632d749c 100644 --- a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py @@ -1,13 +1,2 @@ -import itertools - def combinations(target, size, exclude): - result = [] - possible = [i for i in range(1, target) if i not in exclude] - if size == 1: - return [[target]] - else: - for i in range(len(possible), 0, -1): - for seq in itertools.combinations(possible, i): - if sum(seq) == target and len(seq) == size: - result.append(list(seq)) - return result + pass From 2c102dcef1e0a1325d188c08b0f1db827e135075 Mon Sep 17 00:00:00 2001 From: Meatball Date: Mon, 5 Dec 2022 21:26:06 +0100 Subject: [PATCH 187/826] Fix --- .../killer-sudoku-helper/killer_sudoku_helper.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py index 03632d749c..ba6e873ab2 100644 --- a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py @@ -1,2 +1,13 @@ +import itertools + def combinations(target, size, exclude): - pass + result = [] + if size == 1: + return [[target]] + else: + possible = [index for index in range(1, int((target ** 2 / size) ** 0.6)) if index not in exclude] + for index in range(len(possible), 0, -1): + for seq in itertools.combinations(possible, index): + if sum(seq) == target and len(seq) == size: + result.append(list(seq)) + return result From dcfe63766ccb15f15bfba09d47b964cbbda9258e Mon Sep 17 00:00:00 2001 From: Meatball Date: Mon, 5 Dec 2022 21:28:16 +0100 Subject: [PATCH 188/826] Fixes --- .../practice/killer-sudoku-helper/.meta/example.py | 6 +++--- .../killer-sudoku-helper/killer_sudoku_helper.py | 13 +------------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/exercises/practice/killer-sudoku-helper/.meta/example.py b/exercises/practice/killer-sudoku-helper/.meta/example.py index f5103b776e..ba6e873ab2 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/example.py +++ b/exercises/practice/killer-sudoku-helper/.meta/example.py @@ -2,12 +2,12 @@ def combinations(target, size, exclude): result = [] - possible = [i for i in range(1, target) if i not in exclude] if size == 1: return [[target]] else: - for i in range(len(possible), 0, -1): - for seq in itertools.combinations(possible, i): + possible = [index for index in range(1, int((target ** 2 / size) ** 0.6)) if index not in exclude] + for index in range(len(possible), 0, -1): + for seq in itertools.combinations(possible, index): if sum(seq) == target and len(seq) == size: result.append(list(seq)) return result diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py index ba6e873ab2..03632d749c 100644 --- a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper.py @@ -1,13 +1,2 @@ -import itertools - def combinations(target, size, exclude): - result = [] - if size == 1: - return [[target]] - else: - possible = [index for index in range(1, int((target ** 2 / size) ** 0.6)) if index not in exclude] - for index in range(len(possible), 0, -1): - for seq in itertools.combinations(possible, index): - if sum(seq) == target and len(seq) == size: - result.append(list(seq)) - return result + pass From 3e05a50b2763039cff72e1b938c8c9908d1290f4 Mon Sep 17 00:00:00 2001 From: Meatball Date: Mon, 5 Dec 2022 21:32:42 +0100 Subject: [PATCH 189/826] Changed to 4 as difficutly --- config.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/config.json b/config.json index 748c14b105..f267cbd83a 100644 --- a/config.json +++ b/config.json @@ -1005,22 +1005,6 @@ ], "difficulty": 3 }, - { - "slug": "killer-sudoku-helper", - "name": "Killer Sudoku Helper", - "uuid": "7b16fc93-791b-42a9-8aae-1f78fef2f2f3", - "practices": ["list-comprehensions"], - "prerequisites": [ - "conditionals", - "lists", - "list-methods", - "loops", - "numbers", - "strings", - "string-methods" - ], - "difficulty": 3 - }, { "slug": "bottle-song", "name": "Bottle Song", @@ -1359,6 +1343,22 @@ ], "difficulty": 4 }, + { + "slug": "killer-sudoku-helper", + "name": "Killer Sudoku Helper", + "uuid": "7b16fc93-791b-42a9-8aae-1f78fef2f2f3", + "practices": ["list-comprehensions"], + "prerequisites": [ + "conditionals", + "lists", + "list-methods", + "loops", + "numbers", + "strings", + "string-methods" + ], + "difficulty": 4 + }, { "slug": "tournament", "name": "Tournament", From 30d481c50d816f1bc79ce87f08ea6c8e06130cd6 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 5 Dec 2022 21:36:35 +0100 Subject: [PATCH 190/826] Update exercises/practice/killer-sudoku-helper/.meta/example.py Co-authored-by: BethanyG --- exercises/practice/killer-sudoku-helper/.meta/example.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/exercises/practice/killer-sudoku-helper/.meta/example.py b/exercises/practice/killer-sudoku-helper/.meta/example.py index ba6e873ab2..3da5f2ccfd 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/example.py +++ b/exercises/practice/killer-sudoku-helper/.meta/example.py @@ -2,12 +2,15 @@ def combinations(target, size, exclude): result = [] + possible = [index for index in + range(1, int((target ** 2 /size) ** 0.6)) + if index not in exclude] + if size == 1: return [[target]] else: - possible = [index for index in range(1, int((target ** 2 / size) ** 0.6)) if index not in exclude] for index in range(len(possible), 0, -1): - for seq in itertools.combinations(possible, index): + for seq in itertools.combinations(possible, i): if sum(seq) == target and len(seq) == size: result.append(list(seq)) return result From 87d6924da0fa9d9cf4e618e1e9bc8d9becafbac8 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 5 Dec 2022 21:38:49 +0100 Subject: [PATCH 191/826] Update exercises/practice/killer-sudoku-helper/.meta/example.py Co-authored-by: BethanyG --- exercises/practice/killer-sudoku-helper/.meta/example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/killer-sudoku-helper/.meta/example.py b/exercises/practice/killer-sudoku-helper/.meta/example.py index 3da5f2ccfd..ac3a7f8796 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/example.py +++ b/exercises/practice/killer-sudoku-helper/.meta/example.py @@ -10,7 +10,7 @@ def combinations(target, size, exclude): return [[target]] else: for index in range(len(possible), 0, -1): - for seq in itertools.combinations(possible, i): + for seq in itertools.combinations(possible, index): if sum(seq) == target and len(seq) == size: result.append(list(seq)) return result From 0d28a142d3f54be564991aaa2e64c287e3955b3c Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 06:09:30 -0600 Subject: [PATCH 192/826] Create snippet.md --- .../rna-transcription/.articles/performance/snippet.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 exercises/practice/rna-transcription/.articles/performance/snippet.md diff --git a/exercises/practice/rna-transcription/.articles/performance/snippet.md b/exercises/practice/rna-transcription/.articles/performance/snippet.md new file mode 100644 index 0000000000..f51d300d51 --- /dev/null +++ b/exercises/practice/rna-transcription/.articles/performance/snippet.md @@ -0,0 +1,4 @@ +``` +translate maketrans: 2.502872000914067e-07 +dictionary join: 1.0920033999718725e-06 +``` From caa61dbdaf9f6b4b2b55f2c75a3b03423d896cd1 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 06:12:00 -0600 Subject: [PATCH 193/826] Create config.json --- .../practice/rna-transcription/.articles/config.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 exercises/practice/rna-transcription/.articles/config.json diff --git a/exercises/practice/rna-transcription/.articles/config.json b/exercises/practice/rna-transcription/.articles/config.json new file mode 100644 index 0000000000..0b28ebcbaa --- /dev/null +++ b/exercises/practice/rna-transcription/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "bc3b17f7-a748-4cb3-b44d-e7049e321bc3", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach for RNA Transcription.", + "authors": ["bobahop"] + } + ] +} From 5ce38dbfcc1fbc2a9121f42a75d1f461c2b70224 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 06:12:47 -0600 Subject: [PATCH 194/826] Create Benchmark.py --- .../.articles/performance/code/Benchmark.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 exercises/practice/rna-transcription/.articles/performance/code/Benchmark.py diff --git a/exercises/practice/rna-transcription/.articles/performance/code/Benchmark.py b/exercises/practice/rna-transcription/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..3980aa1748 --- /dev/null +++ b/exercises/practice/rna-transcription/.articles/performance/code/Benchmark.py @@ -0,0 +1,25 @@ +import timeit + +loops = 1_000_000 + +val = timeit.timeit("""to_rna("ACGTGGTCTTAA")""", + """ +LOOKUP = str.maketrans("GCTA","CGAU") + +def to_rna(dna_strand): + return dna_strand.translate(LOOKUP) + +""", number=loops) / loops + +print(f"translate maketrans: {val}") + +val = timeit.timeit("""to_rna("ACGTGGTCTTAA")""", + """ +LOOKUP = {"G": "C", "C": "G", "T": "A", "A": "U"} + +def to_rna(dna_strand): + return ''.join(LOOKUP[chr] for chr in dna_strand) + +""", number=loops) / loops + +print(f"dictionary join: {val}") From 13b0fbf24c3057fb77103d142588f95db519c2d2 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 06:16:38 -0600 Subject: [PATCH 195/826] Create config.json --- .../rna-transcription/.approaches/config.json | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 exercises/practice/rna-transcription/.approaches/config.json diff --git a/exercises/practice/rna-transcription/.approaches/config.json b/exercises/practice/rna-transcription/.approaches/config.json new file mode 100644 index 0000000000..9ab4114548 --- /dev/null +++ b/exercises/practice/rna-transcription/.approaches/config.json @@ -0,0 +1,22 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "9c3d3762-8473-49f2-8004-6d611c958c38", + "slug": "translate-maketrans", + "title": "translate maketrans", + "blurb": "Use translate with maketrans to return the value.", + "authors": ["bobahop"] + }, + { + "uuid": "fbc6be87-dec4-4c4b-84cf-fcc1ed2d6d41", + "slug": "dictionary-join", + "title": "dictionary join", + "blurb": "Use a dictionary look-up with join to return the value.", + "authors": ["bobahop"] + } + ] +} From 2e5efcf01977718a4364edf542a844e02326ecc9 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 06:40:41 -0600 Subject: [PATCH 196/826] Create introduction.md --- .../.approaches/introduction.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 exercises/practice/rna-transcription/.approaches/introduction.md diff --git a/exercises/practice/rna-transcription/.approaches/introduction.md b/exercises/practice/rna-transcription/.approaches/introduction.md new file mode 100644 index 0000000000..fa580afdb4 --- /dev/null +++ b/exercises/practice/rna-transcription/.approaches/introduction.md @@ -0,0 +1,45 @@ +# Introduction + +There are at least two idiomatic approaches to solve RNA Transcription. +One approach is to use `translate` with `maketrans`. +Another approach is to do a dictionary lookup on each character and join the results. + +## General guidance + +Whichever approach is used needs to return the RNA complement for each DNA value. +`translate` with `maketrans` transcribes using the [ASCII][ASCII] values of the characters. +Using a dictionary with `join` transcribes using the string values of the characters. + +## Approach: `translate` with `maketrans` + +```python +LOOKUP = str.maketrans("GCTA", "CGAU") + + +def to_rna(dna_strand): + return dna_strand.translate(LOOKUP) + +``` + +For more information, check the [`translate` with `maketrans` approach][approach-translate-maketrans]. + +## Approach: dictionary look-up with `join` + +```python +LOOKUP = {"G": "C", "C": "G", "T": "A", "A": "U"} + + +def to_rna(dna_strand): + return ''.join(LOOKUP[chr] for chr in dna_strand) + +``` + +For more information, check the [dictionary look-up with `join` approach][approach-dictionary-join]. + +## Which approach to use? + +The `translate` with `maketrans` approach benchmarked over four times faster than the dictionary look-up with `join` approach. + +[ASCII]: https://www.asciitable.com/ +[approach-translate-maketrans]: https://exercism.org/tracks/python/exercises/rna-transcription/approaches/translate-maketrans +[approach-dictionary-join]: https://exercism.org/tracks/python/exercises/rna-transcription/approaches/dictionary-join From a4f312abf41f12dc8d8c0b6bf65656e06b3bb97b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 06:41:46 -0600 Subject: [PATCH 197/826] Create snippet.txt --- .../.approaches/translate-maketrans/snippet.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 exercises/practice/rna-transcription/.approaches/translate-maketrans/snippet.txt diff --git a/exercises/practice/rna-transcription/.approaches/translate-maketrans/snippet.txt b/exercises/practice/rna-transcription/.approaches/translate-maketrans/snippet.txt new file mode 100644 index 0000000000..2d00b83be6 --- /dev/null +++ b/exercises/practice/rna-transcription/.approaches/translate-maketrans/snippet.txt @@ -0,0 +1,5 @@ +LOOKUP = str.maketrans("GCTA", "CGAU") + + +def to_rna(dna_strand): + return dna_strand.translate(LOOKUP) From bf4515ed172100d72aa34b11422372c24db8a644 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 06:42:27 -0600 Subject: [PATCH 198/826] Create snippet.txt --- .../.approaches/dictionary-join/snippet.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 exercises/practice/rna-transcription/.approaches/dictionary-join/snippet.txt diff --git a/exercises/practice/rna-transcription/.approaches/dictionary-join/snippet.txt b/exercises/practice/rna-transcription/.approaches/dictionary-join/snippet.txt new file mode 100644 index 0000000000..558bf98140 --- /dev/null +++ b/exercises/practice/rna-transcription/.approaches/dictionary-join/snippet.txt @@ -0,0 +1,5 @@ +LOOKUP = {"G": "C", "C": "G", "T": "A", "A": "U"} + + +def to_rna(dna_strand): + return ''.join(LOOKUP[chr] for chr in dna_strand) From eae8944e382dcf97a56a7646d3e37275c3c0bd99 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 07:04:00 -0600 Subject: [PATCH 199/826] Create content.md --- .../translate-maketrans/content.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md diff --git a/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md b/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md new file mode 100644 index 0000000000..fcd55730d9 --- /dev/null +++ b/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md @@ -0,0 +1,34 @@ +# `translate()` with `maketrans()` + +```python +LOOKUP = str.maketrans("GCTA", "CGAU") + + +def to_rna(dna_strand): + return dna_strand.translate(LOOKUP) + +``` + +This approach starts by defining a [dictionary][dictionaries] (also called a translation table in this context) by calling the [`maketrans()`][maketrans] method. + +Python doesn't _enforce_ having real constant values, +but the `LOOKUP` translation table is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. +It indicates that the value is not intended to be changed. + +The translation table that is created uses the [ASCII][ASCII] values (also called the ordinal values) for each letter in the two strings. +The ASCII value for "G" in the first string is the key for the ASCII value of "C" in the second string, and so on. + +In the `to_rna()` function, the [`translate()`][translate] method is called on the input, +and is passed the translation table. +The output of `translate()` is a string where all of the input DNA characters have been replaced by their RNA complement in the translation table. + + +```exercism/note +As of this writing, no invalid DNA characters are in the argument to `to_rna()`, so there is no error handling required for invalid input. +``` + +[dictionaries]: https://docs.python.org/3/tutorial/datastructures.html?#dictionaries +[maketrans]: https://docs.python.org/3/library/stdtypes.html?#str.maketrans +[const]: https://realpython.com/python-constants/ +[translate]: https://docs.python.org/3/library/stdtypes.html?#str.translate +[ASCII]: https://www.asciitable.com/ From 80d267beb61167391853affda6fc21e51ac2643d Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 07:07:08 -0600 Subject: [PATCH 200/826] Update introduction.md --- .../.approaches/introduction.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/exercises/practice/rna-transcription/.approaches/introduction.md b/exercises/practice/rna-transcription/.approaches/introduction.md index fa580afdb4..ca2d74a109 100644 --- a/exercises/practice/rna-transcription/.approaches/introduction.md +++ b/exercises/practice/rna-transcription/.approaches/introduction.md @@ -1,16 +1,16 @@ # Introduction There are at least two idiomatic approaches to solve RNA Transcription. -One approach is to use `translate` with `maketrans`. +One approach is to use `translate()` with `maketrans()`. Another approach is to do a dictionary lookup on each character and join the results. ## General guidance Whichever approach is used needs to return the RNA complement for each DNA value. -`translate` with `maketrans` transcribes using the [ASCII][ASCII] values of the characters. -Using a dictionary with `join` transcribes using the string values of the characters. +The `translate()` method with `maketrans()` transcribes using the [ASCII][ASCII] values of the characters. +Using a dictionary look-up with `join()` transcribes using the string values of the characters. -## Approach: `translate` with `maketrans` +## Approach: `translate()` with `maketrans()` ```python LOOKUP = str.maketrans("GCTA", "CGAU") @@ -21,9 +21,9 @@ def to_rna(dna_strand): ``` -For more information, check the [`translate` with `maketrans` approach][approach-translate-maketrans]. +For more information, check the [`translate()` with `maketrans()` approach][approach-translate-maketrans]. -## Approach: dictionary look-up with `join` +## Approach: dictionary look-up with `join()` ```python LOOKUP = {"G": "C", "C": "G", "T": "A", "A": "U"} @@ -34,11 +34,11 @@ def to_rna(dna_strand): ``` -For more information, check the [dictionary look-up with `join` approach][approach-dictionary-join]. +For more information, check the [dictionary look-up with `join()` approach][approach-dictionary-join]. ## Which approach to use? -The `translate` with `maketrans` approach benchmarked over four times faster than the dictionary look-up with `join` approach. +The `translate()` with `maketrans()` approach benchmarked over four times faster than the dictionary look-up with `join()` approach. [ASCII]: https://www.asciitable.com/ [approach-translate-maketrans]: https://exercism.org/tracks/python/exercises/rna-transcription/approaches/translate-maketrans From e5f076f88c03ccf1cef29bfa0475a194d2d61177 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 07:18:15 -0600 Subject: [PATCH 201/826] Create content.md --- .../.approaches/dictionary-join/content.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 exercises/practice/rna-transcription/.approaches/dictionary-join/content.md diff --git a/exercises/practice/rna-transcription/.approaches/dictionary-join/content.md b/exercises/practice/rna-transcription/.approaches/dictionary-join/content.md new file mode 100644 index 0000000000..f3ec1f755f --- /dev/null +++ b/exercises/practice/rna-transcription/.approaches/dictionary-join/content.md @@ -0,0 +1,30 @@ +# dictionary look-up with `join` + +```python +LOOKUP = {"G": "C", "C": "G", "T": "A", "A": "U"} + + +def to_rna(dna_strand): + return ''.join(LOOKUP[chr] for chr in dna_strand) + +``` + +This approach starts by defining a [dictionary][dictionaries] to map the DNA values to RNA values. + +Python doesn't _enforce_ having real constant values, +but the `LOOKUP` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. +It indicates that the value is not intended to be changed. + +In the `to_rna()` function, the [`join()`][join] method is called on an empty string, +and is passed the list created from a [list comprehension][list-comprehension]. + +The list comprehension iterates each character in the input, +looks up the DNA character in the look-up dictionary, and outputs its matching RNA character as an element in the list. + +The `join()` method collects the list of RNA characters back into a string. +Since an empty string is the separator for the `join()`, there are no spaces between the RNA characters in the string. + +[dictionaries]: https://docs.python.org/3/tutorial/datastructures.html?#dictionaries +[const]: https://realpython.com/python-constants/ +[join]: https://docs.python.org/3/library/stdtypes.html?#str.join +[list-comprehension]: https://realpython.com/list-comprehension-python/#using-list-comprehensions From caf3bf1b2b0091f8f6304cec27b1805f9cb8a29c Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 30 Nov 2022 10:31:39 -0600 Subject: [PATCH 202/826] Create content.md --- .../.articles/performance/content.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 exercises/practice/rna-transcription/.articles/performance/content.md diff --git a/exercises/practice/rna-transcription/.articles/performance/content.md b/exercises/practice/rna-transcription/.articles/performance/content.md new file mode 100644 index 0000000000..9419c17567 --- /dev/null +++ b/exercises/practice/rna-transcription/.articles/performance/content.md @@ -0,0 +1,27 @@ +# Performance + +In this approach, we'll find out how to most efficiently calculate the RNA Transcription. + +The [approaches page][approaches] lists two idiomatic approaches to this exercise: + +1. [Using `translate()` with `maketrans()` approach][approach-translate-maketrans] +2. [Using dictionary look-up with `join()` approach][approach-dictionary-join] + + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. + +``` +translate maketrans: 2.502872000914067e-07 +dictionary join: 1.0920033999718725e-06 +``` + +At about `250` nanoseconds, the `translate()` with `maketrans()` approach is more than four times faster than the dictionary with `join()` approach, +which takes about `1092` nanoseconds. + +[approaches]: https://exercism.org/tracks/python/exercises/rna-transcription/approaches +[approach-translate-maketrans]: https://exercism.org/tracks/python/exercises/rna-transcription/approaches/translate-maketrans +[approach-dictionary-join]: https://exercism.org/tracks/python/exercises/rna-transcription/approaches/dictionary-join +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html From 62b9769a9372ee5312f3f37ee80651bd18335e14 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 7 Dec 2022 07:40:49 -0600 Subject: [PATCH 203/826] Update introduction.md --- exercises/practice/bob/.approaches/introduction.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exercises/practice/bob/.approaches/introduction.md b/exercises/practice/bob/.approaches/introduction.md index ccdfa93f90..07d68d1a1e 100644 --- a/exercises/practice/bob/.approaches/introduction.md +++ b/exercises/practice/bob/.approaches/introduction.md @@ -55,8 +55,7 @@ def response(hey_bob): if is_shout: if is_question: return "Calm down, I know what I'm doing!" - else: - return 'Whoa, chill out!' + return 'Whoa, chill out!' if is_question: return 'Sure.' return 'Whatever.' From 2d4d1724cf9dc9a887dc1fb1725c6b6475167d8e Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 7 Dec 2022 07:43:58 -0600 Subject: [PATCH 204/826] Update content.md --- .../bob/.approaches/if-statements-nested/content.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/exercises/practice/bob/.approaches/if-statements-nested/content.md b/exercises/practice/bob/.approaches/if-statements-nested/content.md index 06b5ed527c..5867427afd 100644 --- a/exercises/practice/bob/.approaches/if-statements-nested/content.md +++ b/exercises/practice/bob/.approaches/if-statements-nested/content.md @@ -10,8 +10,7 @@ def response(hey_bob): if is_shout: if is_question: return "Calm down, I know what I'm doing!" - else: - return 'Whoa, chill out!' + return 'Whoa, chill out!' if is_question: return 'Sure.' return 'Whatever.' @@ -21,6 +20,12 @@ def response(hey_bob): In this approach you have a series of `if` statements using the calculated variables to evaluate the conditions, some of which are nested. As soon as a `True` condition is found, the correct response is returned. +```exercism/note +Note that there are no `elif` or `else` statements. +If an `if` statement can return, then an `elif` or `else` is not needed. +Execution will either return or will continue to the next statement anyway. +``` + The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input. If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing. Since it doesn't matter if there is leading whitespace, the `rstrip` function is used instead of [`strip`][strip]. From c29af2e826efe41f0873a393c47aa91b964b0976 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 7 Dec 2022 07:44:41 -0600 Subject: [PATCH 205/826] Update snippet.txt --- .../practice/bob/.approaches/if-statements-nested/snippet.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt b/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt index 0df6c2fe9e..2362a7ac91 100644 --- a/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt +++ b/exercises/practice/bob/.approaches/if-statements-nested/snippet.txt @@ -1,8 +1,7 @@ if is_shout: if is_question: return "Calm down, I know what I'm doing!" - else: - return 'Whoa, chill out!' + return 'Whoa, chill out!' if is_question: return 'Sure.' return 'Whatever.' From 07bbb745c779bbcaf4955e0f03189246c6a30ef7 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 7 Dec 2022 07:48:23 -0600 Subject: [PATCH 206/826] Update Benchmark.py --- exercises/practice/bob/.articles/performance/code/Benchmark.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exercises/practice/bob/.articles/performance/code/Benchmark.py b/exercises/practice/bob/.articles/performance/code/Benchmark.py index e726ed1953..3e5a011fcb 100644 --- a/exercises/practice/bob/.articles/performance/code/Benchmark.py +++ b/exercises/practice/bob/.articles/performance/code/Benchmark.py @@ -34,8 +34,7 @@ def response(hey_bob): if is_shout: if is_question: return "Calm down, I know what I'm doing!" - else: - return 'Whoa, chill out!' + return 'Whoa, chill out!' if is_question: return 'Sure.' return 'Whatever.' From ce0a2593ccdc3ff44d996b76403a67bfe8d9d7bf Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Wed, 7 Dec 2022 22:25:34 +0100 Subject: [PATCH 207/826] fix --- .../locomotive_engineer_test.py | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py index b3a46939fd..d3d3178fdf 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py @@ -15,7 +15,7 @@ def test_get_list_of_wagons(self): output_data = [[1,5,2,7,4], [1,5], [1], [1,9,3], [1,10,6,3,9,8,4,14,24,7]] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different wagon list instead.' self.assertEqual(get_list_of_wagons(*input_data), output_data, msg=error_msg) @@ -32,59 +32,59 @@ def test_fix_list_of_wagons(self): [1, 8, 6, 4, 5, 9, 21, 2, 13, 25, 7, 19, 10, 3, 14] ] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different wagon list instead.' self.assertEqual(fix_list_of_wagons(input_data[0], input_data[1]), output_data, msg=error_msg) @pytest.mark.task(taskno=3) def test_add_missing_stops(self): - input_data = (({"from": "Berlin", "to": "Hamburg"}, {"stop_1": "Lepzig", "stop_2": "Hannover", "stop_3": "Frankfurt"}), - ({"from": "Paris", "to": "London"}, {"stop_1": "Lille"}), - ({"from": "New York", "to": "Philadelphia"},{}), - ({"from": "Gothenburg", "to": "Copenhagen"}, {"stop_1": "Kungsbacka", "stop_2": "Varberg", "stop_3": "Halmstad", "stop_4": "Angelholm", "stop_5": "Lund", "stop_6": "Malmo"}) + input_data = (({'from': 'Berlin', 'to': 'Hamburg'}, {'stop_1': 'Lepzig', 'stop_2': 'Hannover', 'stop_3': 'Frankfurt'}), + ({'from': 'Paris', 'to': 'London'}, {'stop_1': 'Lille'}), + ({'from': 'New York', 'to': 'Philadelphia'},{}), + ({'from': 'Gothenburg', 'to': 'Copenhagen'}, {'stop_1': 'Kungsbacka', 'stop_2': 'Varberg', 'stop_3': 'Halmstad', 'stop_4': 'Angelholm', 'stop_5': 'Lund', 'stop_6': 'Malmo'}) ) - output_data = [{"from": "Berlin", "to": "Hamburg", "stops": ["Lepzig", "Hannover", "Frankfurt"]}, - {"from": "Paris", "to": "London", "stops": ["Lille"]}, - {"from": "New York", "to": "Philadelphia", "stops": []}, - {"from": "Gothenburg", "to": "Copenhagen", "stops": ["Kungsbacka", "Varberg", "Halmstad", "Angelholm", "Lund", "Malmo"]} + output_data = [{'from': 'Berlin', 'to': 'Hamburg', 'stops': ['Lepzig', 'Hannover', 'Frankfurt']}, + {'from': 'Paris', 'to': 'London', 'stops': ['Lille']}, + {'from': 'New York', 'to': 'Philadelphia', 'stops': []}, + {'from': 'Gothenburg', 'to': 'Copenhagen', 'stops': ['Kungsbacka', 'Varberg', 'Halmstad', 'Angelholm', 'Lund', 'Malmo']} ] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different set of stops instead.' self.assertEqual(add_missing_stops(input_data[0], **input_data[1]), output_data, msg=error_msg) @pytest.mark.task(taskno=4) def test_extend_route_information(self): - input_data = [({"from": "Berlin", "to": "Hamburg"}, {"timeOfArrival": "12:00", "precipitation": "10", "temperature": "5", "caboose": "yes"}), - ({"from": "Paris", "to": "London"}, {"timeOfArrival": "10:30", "temperature": "20", "length": 15}), - ({"from": "Gothenburg", "to": "Copenhagen"}, {"precipitation": "1", "timeOfArrival": "21:20", "temperature": "-6"})] - output_data = [{"from": "Berlin", "to": "Hamburg", "timeOfArrival": "12:00", "precipitation": "10", "temperature": "5", "caboose": "yes"}, - {"from": "Paris", "to": "London", "timeOfArrival": "10:30", "temperature": "20", "length": 15}, - {"from": "Gothenburg", "to": "Copenhagen", "precipitation": "1", "timeOfArrival": "21:20", "temperature": "-6"} + input_data = [({'from': 'Berlin', 'to': 'Hamburg'}, {'timeOfArrival': '12:00', 'precipitation': '10', 'temperature': '5', 'caboose': 'yes'}), + ({'from': 'Paris', 'to': 'London'}, {'timeOfArrival': '10:30', 'temperature': '20', 'length': '15'}), + ({'from': 'Gothenburg', 'to': 'Copenhagen'}, {'precipitation': '1', 'timeOfArrival': '21:20', 'temperature': '-6'})] + output_data = [{'from': 'Berlin', 'to': 'Hamburg', 'timeOfArrival': '12:00', 'precipitation': '10', 'temperature': '5', 'caboose': 'yes'}, + {'from': 'Paris', 'to': 'London', 'timeOfArrival': '10:30', 'temperature': '20', 'length': '15'}, + {'from': 'Gothenburg', 'to': 'Copenhagen', 'precipitation': '1', 'timeOfArrival': '21:20', 'temperature': '-6'} ] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different route dictionary instead.' self.assertEqual(extend_route_information(input_data[0], input_data[1]), output_data, msg=error_msg) @pytest.mark.task(taskno=5) def test_fix_wagon_depot(self): input_data = ( - [[(2, "red"), (4, "red"), (8, "red")], [(5, "blue"), (9, "blue"), (13, "blue")], [(3, "orange"), (7, "orange"), (11, "orange")]], - [[(6, "blue"), (10, "blue"), (14, "blue")], [(7, "red"), (4, "red"), (2, "red")], [(3, "orange"), (11, "orange"), (15, "orange")]], - [[(7, "pink"), (4, "pink"), (2, "pink")],[(10, "green"), (6, "green"), (14, "green")], [(9, "yellow"), (5, "yellow"), (13, "yellow")]], - [[(3, "purple"), (11, "purple"), (15, "purple")], [(20, "black"), (16, "black"), (12, "black")], [(19, "white"), (17, "white"), (18, "white")]] + [[(2, 'red'), (4, 'red'), (8, 'red')], [(5, 'blue'), (9, 'blue'), (13, 'blue')], [(3, 'orange'), (7, 'orange'), (11, 'orange')]], + [[(6, 'blue'), (10, 'blue'), (14, 'blue')], [(7, 'red'), (4, 'red'), (2, 'red')], [(3, 'orange'), (11, 'orange'), (15, 'orange')]], + [[(7, 'pink'), (4, 'pink'), (2, 'pink')], [(10, 'green'), (6, 'green'), (14, 'green')], [(9, 'yellow'), (5, 'yellow'), (13, 'yellow')]], + [[(3, 'purple'), (11, 'purple'), (15, 'purple')], [(20, 'black'), (16, 'black'), (12, 'black')], [(19, 'white'), (17, 'white'), (18, 'white')]] ) output_data = ( - [[(2, "red"), (5, "blue"), (3, "orange")],[(4, "red"), (9, "blue"), (7, "orange")], [(8, "red"), (13, "blue"), (11, "orange")]], - [[(6, "blue"), (7, "red"), (3, "orange")],[(10, "blue"), (4, "red"), (11, "orange")], [(14, "blue"), (2, "red"), (15, "orange")]], - [[(7, "pink"), (10, "green"), (9, "yellow")], [(4, "pink"), (6, "green"), (5, "yellow")], [(2, "pink"), (14, "green"), (13, "yellow")]], - [[(3, "purple"), (20, "black"), (19, "white")], [(11, "purple"), (16, "black"), (17, "white")], [(15, "purple"), (12, "black"), (18, "white")]] + [[(2, 'red'), (5, 'blue'), (3, 'orange')], [(4, 'red'), (9, 'blue'), (7, 'orange')], [(8, 'red'), (13, 'blue'), (11, 'orange')]], + [[(6, 'blue'), (7, 'red'), (3, 'orange')], [(10, 'blue'), (4, 'red'), (11, 'orange')], [(14, 'blue'), (2, 'red'), (15, 'orange')]], + [[(7, 'pink'), (10, 'green'), (9, 'yellow')], [(4, 'pink'), (6, 'green'), (5, 'yellow')], [(2, 'pink'), (14, 'green'), (13, 'yellow')]], + [[(3, 'purple'), (20, 'black'), (19, 'white')], [(11, 'purple'), (16, 'black'), (17, 'white')], [(15, 'purple'), (12, 'black'), (18, 'white')]] ) for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different wagon depot list instead.' self.assertEqual(fix_wagon_depot(input_data), output_data, msg=error_msg) From 154a809493e4f41a44942c0a17d5755226045d1b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 06:47:54 -0600 Subject: [PATCH 208/826] Create config.json --- .../practice/luhn/.approaches/config.json | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 exercises/practice/luhn/.approaches/config.json diff --git a/exercises/practice/luhn/.approaches/config.json b/exercises/practice/luhn/.approaches/config.json new file mode 100644 index 0000000000..37536d78c1 --- /dev/null +++ b/exercises/practice/luhn/.approaches/config.json @@ -0,0 +1,29 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "568ca4f5-1046-4750-b3c2-6a50b84ed8ed", + "slug": "reversed-for", + "title": "reversed for loop", + "blurb": "Use reversed with a for loop to validate the number.", + "authors": ["bobahop"] + }, + { + "uuid": "24d6c283-3a7d-4315-91db-b3160f3df567", + "slug": "replace-reverse-enumerate", + "title": "replace, reverse, enumerate", + "blurb": "Use replace, reverse, and enumerate to validate the number.", + "authors": ["bobahop"] + }, + { + "uuid": "fca6a89a-baa9-4aeb-b419-130d6ad14564", + "slug": "recursion", + "title": "recursion", + "blurb": "Use recursion to validate the number.", + "authors": ["bobahop"] + } + ] +} From 2b0d5b7a0e15611ad93a3cae1059a59e8524dde3 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 06:49:40 -0600 Subject: [PATCH 209/826] Create config.json --- exercises/practice/luhn/.articles/config.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 exercises/practice/luhn/.articles/config.json diff --git a/exercises/practice/luhn/.articles/config.json b/exercises/practice/luhn/.articles/config.json new file mode 100644 index 0000000000..132e55513e --- /dev/null +++ b/exercises/practice/luhn/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "fa3d646c-b075-49a4-80b7-b339f9022aea", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach to validating Luhn.", + "authors": ["bobahop"] + } + ] +} From de25ba653df2dc43146be1c4a10da1b6f2de0adb Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 06:50:27 -0600 Subject: [PATCH 210/826] Create snippet.md --- exercises/practice/luhn/.articles/performance/snippet.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 exercises/practice/luhn/.articles/performance/snippet.md diff --git a/exercises/practice/luhn/.articles/performance/snippet.md b/exercises/practice/luhn/.articles/performance/snippet.md new file mode 100644 index 0000000000..de2b9ca564 --- /dev/null +++ b/exercises/practice/luhn/.articles/performance/snippet.md @@ -0,0 +1,5 @@ +``` +reversed for: 1.1550310099963099e-05 +replace reverse enumerate: 1.0071774299954995e-05 +recursion: 2.4321520800003783e-05 +``` From fa11649647278783f1d772c8f61c1994d248c2db Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 06:51:03 -0600 Subject: [PATCH 211/826] Create Benchmark.py --- .../.articles/performance/code/Benchmark.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 exercises/practice/luhn/.articles/performance/code/Benchmark.py diff --git a/exercises/practice/luhn/.articles/performance/code/Benchmark.py b/exercises/practice/luhn/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..2f95127822 --- /dev/null +++ b/exercises/practice/luhn/.articles/performance/code/Benchmark.py @@ -0,0 +1,104 @@ +import timeit + +loops = 1_000_000 + +val = timeit.timeit("""Luhn("9999999999 9999999999 9999999999 9999999999").valid()""", + """ +class Luhn: + + def __init__(self, card_num): + self.isValid = Luhn.luhny_bin(card_num) + + def valid(self): + return self.isValid + + @staticmethod + def luhny_tune(num): + return dbl - 9 if (dbl := 2 * num) > 9 else dbl + + @staticmethod + def luhny_bin(num): + total = 0 + pos = 0 + for ltr in reversed(num): + if ltr == " ": + continue + if not ltr.isdigit(): + return False + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 + return pos > 1 and not total % 10 + + +""", number=loops) / loops + +print(f"reversed for: {val}") + +val = timeit.timeit("""Luhn("9999999999 9999999999 9999999999 9999999999").valid()""", + """ +class Luhn: + + def __init__(self, card_num): + self.isValid = Luhn.luhny_bin(card_num) + + def valid(self): + return self.isValid + + @staticmethod + def luhny_tune(num): + return dbl - 9 if (dbl := 2 * num) > 9 else dbl + + @staticmethod + def luhny_bin(num): + num = num.replace(' ', '') + if not num.isdigit(): + return False + total = 0 + for pos, ltr in enumerate(num[::-1]): + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 + return pos > 1 and not total % 10 + +""", number=loops) / loops + +print(f"replace reverse enumerate: {val}") + +val = timeit.timeit("""Luhn("9999999999 9999999999 9999999999 9999999999").valid()""", + """ +class Luhn: + def __init__(self, card_num): + self.isValid = Luhn.luhny_bin(0, 0, list(card_num[::-1])) + + def valid(self): + return self.isValid + + @staticmethod + def luhny_tune(num): + return dbl - 9 if (dbl := 2 * num) > 9 else dbl + + @staticmethod + def luhny_bin(pos, sum, chars): + if not chars: + if pos < 2: + return False + return sum % 10 == 0 + else: + head, *tail = chars + if head.isdigit(): + if pos % 2 == 0: + return Luhn.luhny_bin(pos + 1, sum + int(head), tail) + else: + return Luhn.luhny_bin(pos + 1, sum + Luhn.luhny_tune(int(head)), tail) + if head == " ": + return Luhn.luhny_bin(pos, sum, tail) + return False + +""", number=loops) / loops + +print(f"recursion: {val}") From d9e5d3a991051860aef7294355a0508ed880b894 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 07:12:32 -0600 Subject: [PATCH 212/826] Create introduction.md --- .../practice/luhn/.approaches/introduction.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 exercises/practice/luhn/.approaches/introduction.md diff --git a/exercises/practice/luhn/.approaches/introduction.md b/exercises/practice/luhn/.approaches/introduction.md new file mode 100644 index 0000000000..378537c17f --- /dev/null +++ b/exercises/practice/luhn/.approaches/introduction.md @@ -0,0 +1,93 @@ +# Introduction + +There are many idiomatic ways to solve Luhn. +Among them are: +- You can use a for loop on a `reversed()` iterator. +- You can scrub the input with `replace()` and test with `isdigit()` before reversing the input to `enumerate()` it. + +## General guidance + +One important aspect to solving Luhn is to allow for spaces in the input and to disallow all other non-numeric characters. +Another important aspect is to handle the value of each digit according to its position in the string. +Another consideration may be to calculate the validity only once, no matter how many times `valid()` is called. + +## Approach: `reversed()` `for` loop + +```python +class Luhn: + + def __init__(self, card_num): + self.isValid = Luhn.luhny_bin(card_num) + + def valid(self): + return self.isValid + + @staticmethod + def luhny_tune(num): + return dbl - 9 if (dbl := 2 * num) > 9 else dbl + + @staticmethod + def luhny_bin(num): + total = 0 + pos = 0 + for ltr in reversed(num): + if ltr == " ": + continue + if not ltr.isdigit(): + return False + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 + return pos > 1 and not total % 10 + +``` + +For more information, check the [`reversed()` `for` loop approach][approach-reversed-for]. + +## Approach: `replace()`, reverse, `enumerate()` + +```python +class Luhn: + + def __init__(self, card_num): + self.isValid = Luhn.luhny_bin(card_num) + + def valid(self): + return self.isValid + + @staticmethod + def luhny_tune(num): + return dbl - 9 if (dbl := 2 * num) > 9 else dbl + + @staticmethod + def luhny_bin(num): + num = num.replace(' ', '') + if not num.isdigit(): + return False + total = 0 + for pos, ltr in enumerate(num[::-1]): + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 + return pos > 1 and not total % 10 + +``` + +For more information, check the [`replace()`, reverse, `enumerate()` approach][approach-replace-reverse-enumerate]. + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +### Other approach: recursion + +Another approach can use recursion to validate the number. +For more information, check the [recursion approach][approach-recursion]. + +[approach-reversed-for]: https://exercism.org/tracks/python/exercises/luhn/approaches/reversed-for +[approach-replace-reverse-enumerate]: https://exercism.org/tracks/python/exercises/luhn/approaches/replace-reverse-enumerate +[approach-recursion]: https://exercism.org/tracks/python/exercises/luhn/approaches/recursion From 661677505bba98dd73f7fbbb520d71c66b64ace0 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 07:14:17 -0600 Subject: [PATCH 213/826] Create content.md --- .../luhn/.approaches/recursion/content.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 exercises/practice/luhn/.approaches/recursion/content.md diff --git a/exercises/practice/luhn/.approaches/recursion/content.md b/exercises/practice/luhn/.approaches/recursion/content.md new file mode 100644 index 0000000000..a4f682c4e7 --- /dev/null +++ b/exercises/practice/luhn/.approaches/recursion/content.md @@ -0,0 +1,32 @@ +# Recursion + +```python +class Luhn: + def __init__(self, card_num): + self.isValid = Luhn.luhny_bin(0, 0, list(card_num[::-1])) + + def valid(self): + return self.isValid + + @staticmethod + def luhny_tune(num): + return dbl - 9 if (dbl := 2 * num) > 9 else dbl + + @staticmethod + def luhny_bin(pos, sum, chars): + if not chars: + if pos < 2: + return False + return sum % 10 == 0 + else: + head, *tail = chars + if head.isdigit(): + if pos % 2 == 0: + return Luhn.luhny_bin(pos + 1, sum + int(head), tail) + else: + return Luhn.luhny_bin(pos + 1, sum + Luhn.luhny_tune(int(head)), tail) + if head == " ": + return Luhn.luhny_bin(pos, sum, tail) + return False + +``` From 8dca95d30ae40d0aae7add69ae6d0e438df2f2b0 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 07:20:54 -0600 Subject: [PATCH 214/826] Update snippet.md --- exercises/practice/luhn/.articles/performance/snippet.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/luhn/.articles/performance/snippet.md b/exercises/practice/luhn/.articles/performance/snippet.md index de2b9ca564..532b085ce2 100644 --- a/exercises/practice/luhn/.articles/performance/snippet.md +++ b/exercises/practice/luhn/.articles/performance/snippet.md @@ -1,5 +1,5 @@ ``` -reversed for: 1.1550310099963099e-05 -replace reverse enumerate: 1.0071774299954995e-05 +reversed for: 1.0783263299963438e-05 +replace reverse enumerate: 9.933844099985436e-06 recursion: 2.4321520800003783e-05 ``` From ee40d4d4bd32918f1f5fc627137cf674c4b4464e Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 07:23:39 -0600 Subject: [PATCH 215/826] Update Benchmark.py --- .../luhn/.articles/performance/code/Benchmark.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/exercises/practice/luhn/.articles/performance/code/Benchmark.py b/exercises/practice/luhn/.articles/performance/code/Benchmark.py index 2f95127822..66afd6f49e 100644 --- a/exercises/practice/luhn/.articles/performance/code/Benchmark.py +++ b/exercises/practice/luhn/.articles/performance/code/Benchmark.py @@ -21,18 +21,16 @@ def luhny_bin(num): total = 0 pos = 0 for ltr in reversed(num): - if ltr == " ": - continue - if not ltr.isdigit(): + if ltr.isdigit(): + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 + elif ltr != " ": return False - if not pos % 2: - total+= int(ltr) - else: - total += Luhn.luhny_tune(int(ltr)) - pos += 1 return pos > 1 and not total % 10 - """, number=loops) / loops print(f"reversed for: {val}") From bd450818d35e4dbfa9e212eb4e0ee62a938db482 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 07:25:28 -0600 Subject: [PATCH 216/826] Create snippet.txt --- exercises/practice/luhn/.approaches/recursion/snippet.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 exercises/practice/luhn/.approaches/recursion/snippet.txt diff --git a/exercises/practice/luhn/.approaches/recursion/snippet.txt b/exercises/practice/luhn/.approaches/recursion/snippet.txt new file mode 100644 index 0000000000..c59dd9a5fe --- /dev/null +++ b/exercises/practice/luhn/.approaches/recursion/snippet.txt @@ -0,0 +1,8 @@ +head, *tail = chars +if head.isdigit(): + if pos % 2 == 0: + return Luhn.luhny_bin(pos + 1, sum + int(head), tail) + else: + return Luhn.luhny_bin(pos + 1, sum + Luhn.luhny_tune(int(head)), tail) +if head == " ": + return Luhn.luhny_bin(pos, sum, tail) From 8c895d6cbf488b5258f9bbdffee8e09925c40c9f Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 11:14:49 -0600 Subject: [PATCH 217/826] Update introduction.md --- .../practice/luhn/.approaches/introduction.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/exercises/practice/luhn/.approaches/introduction.md b/exercises/practice/luhn/.approaches/introduction.md index 378537c17f..904d1e2036 100644 --- a/exercises/practice/luhn/.approaches/introduction.md +++ b/exercises/practice/luhn/.approaches/introduction.md @@ -31,15 +31,14 @@ class Luhn: total = 0 pos = 0 for ltr in reversed(num): - if ltr == " ": - continue - if not ltr.isdigit(): + if ltr.isdigit(): + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 + elif ltr != " ": return False - if not pos % 2: - total+= int(ltr) - else: - total += Luhn.luhny_tune(int(ltr)) - pos += 1 return pos > 1 and not total % 10 ``` From 7f01407de8786b92af48e77c9c0861e5ad2ff2b9 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 11:41:09 -0600 Subject: [PATCH 218/826] Create content.md --- .../luhn/.approaches/reversed-for/content.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 exercises/practice/luhn/.approaches/reversed-for/content.md diff --git a/exercises/practice/luhn/.approaches/reversed-for/content.md b/exercises/practice/luhn/.approaches/reversed-for/content.md new file mode 100644 index 0000000000..24d2897b32 --- /dev/null +++ b/exercises/practice/luhn/.approaches/reversed-for/content.md @@ -0,0 +1,73 @@ +# `reversed()` with a `for` loop + +```python +class Luhn: + + def __init__(self, card_num): + self.isValid = Luhn.luhny_bin(card_num) + + def valid(self): + return self.isValid + + @staticmethod + def luhny_tune(num): + return dbl - 9 if (dbl := 2 * num) > 9 else dbl + + @staticmethod + def luhny_bin(num): + total = 0 + pos = 0 + for ltr in reversed(num): + if ltr.isdigit(): + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 + elif ltr != " ": + return False + return pos > 1 and not total % 10 + +``` + +The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The result of the validation is returned from `Luhn`'s `valid()` method. +In this approach, a member variable is set to the result of running the Luhn algorithm. +That variable is returned from the `valid()` method. + +The methods that do the work have the [`@staticmethod`][static-method] decorator. +This indicates that the method belongs to the class and is not recreated for every object instance. + +In the code example the `luhny_bin` method initializes the `total` and `pos` variables to `0`. +It then calls [`reversed()`][reversed] on the input and iterates the characters from right to left with a [`for`][for] loop. + +The [`isdigit()`][isdigit] method is used to see if the character is a digit. +The [modulo operator][modulo-operator] is used to check if the character's position is evenly divided by `2`. +By using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. +It can be thought of as the expression _not_ having a remainder. + +If the position is evenly divided by `2`, then it is even, and the character is converted to an [`int()`][int] and added to the total variable. + +If the position is odd, then the number is converted to an `int` and is passed to the static method which always doubles it, +and will subtract `9` from the doubled value if the doubled value is greater than `9`. +It does this using a [ternary operator][ternary-operator]. +Inside the ternary operator an [assignment expression][assignment-expression] assigns the doubled value to the `dbl` variable with `(dbl := 2 * num)` +The ternary operator returns `dbl - 9` if `dbl` is greater than `9`, otherwise it returns `dbl`. +The resulting value is added to the total variable. + +Whether the digit is even or odd, the position is incremented by `1`. +If the character is not a digit, the the function returns `False` if the character is not a space. +If the character is a space, the loop will go to the next iteration without incrementing the position variable. + +After the iteration of the characters is done, the function return if the position is greater than `1` and if the total is evenly divisible by `10`. + +[static-method]: https://docs.python.org/3/library/functions.html?#staticmethod +[reversed]: https://docs.python.org/3/library/functions.html?#reversed +[for]: https://docs.python.org/3/tutorial/controlflow.html#for-statements +[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit +[modulo-operator]: https://realpython.com/python-modulo-operator/ +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[not-operator]: https://realpython.com/python-not-operator/ +[int]: https://docs.python.org/3/library/functions.html?#int +[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ +[assignment-expression]: https://peps.python.org/pep-0572/ From 71d468ae80fefde30849903e433cd99942818d14 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 1 Dec 2022 11:42:51 -0600 Subject: [PATCH 219/826] Create snippet.txt --- .../practice/luhn/.approaches/reversed-for/snippet.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 exercises/practice/luhn/.approaches/reversed-for/snippet.txt diff --git a/exercises/practice/luhn/.approaches/reversed-for/snippet.txt b/exercises/practice/luhn/.approaches/reversed-for/snippet.txt new file mode 100644 index 0000000000..46e9ec07fc --- /dev/null +++ b/exercises/practice/luhn/.approaches/reversed-for/snippet.txt @@ -0,0 +1,8 @@ +if ltr.isdigit(): + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 +elif ltr != " ": + return False From 53c714185e9dd20542e37c9ddd84ae3728adaeb5 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 06:05:56 -0600 Subject: [PATCH 220/826] Create content.md --- .../replace-reverse-enumerate/content.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md diff --git a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md new file mode 100644 index 0000000000..8b2a5919d9 --- /dev/null +++ b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md @@ -0,0 +1,73 @@ +# `replace()`, reverse, `enumerate()` + +```python +class Luhn: + + def __init__(self, card_num): + self.isValid = Luhn.luhny_bin(card_num) + + def valid(self): + return self.isValid + + @staticmethod + def luhny_tune(num): + return dbl - 9 if (dbl := 2 * num) > 9 else dbl + + @staticmethod + def luhny_bin(num): + num = num.replace(' ', '') + if not num.isdigit(): + return False + total = 0 + for pos, ltr in enumerate(num[::-1]): + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 + return pos > 1 and not total % 10 + +``` + +The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The result of the validation is returned from `Luhn`'s `valid()` method. +In this approach, a member variable is set to the result of running the Luhn algorithm. +That variable is returned from the `valid()` method. + +The methods that do the work have the [`@staticmethod`][static-method] decorator. +This indicates that the method belongs to the class and is not recreated for every object instance. + +In the code example the `luhny_bin` method uses the [`replace()`][replace] method to replace all occurrences of a space in the input with an empty string. +The [`isdigit()`][isdigit] method is then used to see if all of the remaining characters are digits. +If not, the function returns `False`. + +[Slicing][slicing] syntax (`[::-1`) is used to reverse the characters in the input, which is then passed into the [`enumerate()`][enumerate] method. +The [`for`][for] loop uses `enumerate()` to iterate the reversed characters in the input, returning the character and its position in the string. + +The [modulo operator][modulo-operator] is used to check if the character's position is evenly divided by `2`. +By using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. +It can be thought of as the expression _not_ having a remainder. + +If the position is evenly divided by `2`, then it is even, and the character is converted to an [`int()`][int] and added to the total variable. + +If the position is odd, then the number is converted to an `int` and is passed to the static method which always doubles it, +and will subtract `9` from the doubled value if the doubled value is greater than `9`. +It does this using a [ternary operator][ternary-operator]. +Inside the ternary operator an [assignment expression][assignment-expression] assigns the doubled value to the `dbl` variable with `(dbl := 2 * num)`. +The ternary operator returns `dbl - 9` if `dbl` is greater than `9`, otherwise it returns `dbl`. +The resulting value is added to the total variable. + +After the iteration of the characters is done, the function return if the position is greater than `1` and if the total is evenly divisible by `10`. + +[static-method]: https://docs.python.org/3/library/functions.html?#staticmethod +[replace]: https://docs.python.org/3/library/stdtypes.html?#str.replace +[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit +[enumerate]: https://docs.python.org/3/library/functions.html?#enumerate +[slicing]: https://www.learnbyexample.org/python-string-slicing/ +[for]: https://docs.python.org/3/tutorial/controlflow.html#for-statements +[modulo-operator]: https://realpython.com/python-modulo-operator/ +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[not-operator]: https://realpython.com/python-not-operator/ +[int]: https://docs.python.org/3/library/functions.html?#int +[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ +[assignment-expression]: https://peps.python.org/pep-0572/ From ea5ec24997851c517069c14666d37309914be7e3 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 06:06:21 -0600 Subject: [PATCH 221/826] Update content.md --- exercises/practice/luhn/.approaches/reversed-for/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/luhn/.approaches/reversed-for/content.md b/exercises/practice/luhn/.approaches/reversed-for/content.md index 24d2897b32..58bd061b2b 100644 --- a/exercises/practice/luhn/.approaches/reversed-for/content.md +++ b/exercises/practice/luhn/.approaches/reversed-for/content.md @@ -51,7 +51,7 @@ If the position is evenly divided by `2`, then it is even, and the character is If the position is odd, then the number is converted to an `int` and is passed to the static method which always doubles it, and will subtract `9` from the doubled value if the doubled value is greater than `9`. It does this using a [ternary operator][ternary-operator]. -Inside the ternary operator an [assignment expression][assignment-expression] assigns the doubled value to the `dbl` variable with `(dbl := 2 * num)` +Inside the ternary operator an [assignment expression][assignment-expression] assigns the doubled value to the `dbl` variable with `(dbl := 2 * num)`. The ternary operator returns `dbl - 9` if `dbl` is greater than `9`, otherwise it returns `dbl`. The resulting value is added to the total variable. From 934dde9a684ab51aa9bedcd5f601150dcef53893 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 06:07:54 -0600 Subject: [PATCH 222/826] Create snippet.txt --- .../luhn/.approaches/replace-reverse-enumerate/snippet.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 exercises/practice/luhn/.approaches/replace-reverse-enumerate/snippet.txt diff --git a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/snippet.txt b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/snippet.txt new file mode 100644 index 0000000000..d02d2c5a2d --- /dev/null +++ b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/snippet.txt @@ -0,0 +1,7 @@ +for pos, ltr in enumerate(num[::-1]): + if not pos % 2: + total+= int(ltr) + else: + total += Luhn.luhny_tune(int(ltr)) + pos += 1 +return pos > 1 and not total % 10 From 6471b042a34fde874d708255e54143d11ffa5782 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 06:57:46 -0600 Subject: [PATCH 223/826] Update Benchmark.py --- .../practice/luhn/.articles/performance/code/Benchmark.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/exercises/practice/luhn/.articles/performance/code/Benchmark.py b/exercises/practice/luhn/.articles/performance/code/Benchmark.py index 66afd6f49e..21d4d2f26b 100644 --- a/exercises/practice/luhn/.articles/performance/code/Benchmark.py +++ b/exercises/practice/luhn/.articles/performance/code/Benchmark.py @@ -83,9 +83,7 @@ def luhny_tune(num): @staticmethod def luhny_bin(pos, sum, chars): if not chars: - if pos < 2: - return False - return sum % 10 == 0 + return pos > 1 and sum % 10 == 0 else: head, *tail = chars if head.isdigit(): From 3668b80b19c5a015ec0393b99d6e1bc846856a8b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:05:53 -0600 Subject: [PATCH 224/826] Update content.md --- exercises/practice/luhn/.approaches/reversed-for/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/luhn/.approaches/reversed-for/content.md b/exercises/practice/luhn/.approaches/reversed-for/content.md index 58bd061b2b..291f5df13a 100644 --- a/exercises/practice/luhn/.approaches/reversed-for/content.md +++ b/exercises/practice/luhn/.approaches/reversed-for/content.md @@ -42,7 +42,7 @@ In the code example the `luhny_bin` method initializes the `total` and `pos` var It then calls [`reversed()`][reversed] on the input and iterates the characters from right to left with a [`for`][for] loop. The [`isdigit()`][isdigit] method is used to see if the character is a digit. -The [modulo operator][modulo-operator] is used to check if the character's position is evenly divided by `2`. +If so, the [modulo operator][modulo-operator] is used to check if the character's position is evenly divided by `2`. By using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. It can be thought of as the expression _not_ having a remainder. From 7322348adfae497d6036c9f7a400def098b255df Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:06:48 -0600 Subject: [PATCH 225/826] Update Benchmark.py --- exercises/practice/luhn/.articles/performance/code/Benchmark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/luhn/.articles/performance/code/Benchmark.py b/exercises/practice/luhn/.articles/performance/code/Benchmark.py index 21d4d2f26b..27372f5282 100644 --- a/exercises/practice/luhn/.articles/performance/code/Benchmark.py +++ b/exercises/practice/luhn/.articles/performance/code/Benchmark.py @@ -87,7 +87,7 @@ def luhny_bin(pos, sum, chars): else: head, *tail = chars if head.isdigit(): - if pos % 2 == 0: + if not pos % 2: return Luhn.luhny_bin(pos + 1, sum + int(head), tail) else: return Luhn.luhny_bin(pos + 1, sum + Luhn.luhny_tune(int(head)), tail) From e8ab1cd663c4140bdf64306772b785b139a4e2eb Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:22:37 -0600 Subject: [PATCH 226/826] Update content.md --- .../luhn/.approaches/recursion/content.md | 72 +++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/exercises/practice/luhn/.approaches/recursion/content.md b/exercises/practice/luhn/.approaches/recursion/content.md index a4f682c4e7..0c146cbdcd 100644 --- a/exercises/practice/luhn/.approaches/recursion/content.md +++ b/exercises/practice/luhn/.approaches/recursion/content.md @@ -15,13 +15,11 @@ class Luhn: @staticmethod def luhny_bin(pos, sum, chars): if not chars: - if pos < 2: - return False - return sum % 10 == 0 + return pos > 1 and sum % 10 == 0 else: head, *tail = chars if head.isdigit(): - if pos % 2 == 0: + if not pos % 2: return Luhn.luhny_bin(pos + 1, sum + int(head), tail) else: return Luhn.luhny_bin(pos + 1, sum + Luhn.luhny_tune(int(head)), tail) @@ -30,3 +28,69 @@ class Luhn: return False ``` + +The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The result of the validation is returned from `Luhn`'s `valid()` method. +In this approach, a member variable is set to the result of running the Luhn algorithm. +That variable is returned from the `valid()` method. + +The methods that do the work have the [`@staticmethod`][static-method] decorator. +This indicates that the method belongs to the class and is not recreated for every object instance. + +In the code example the `__init__` method uses [slicing][slicing] syntax (`[::-1]`) to reverse the characters in the input, +which is then passed into the [list][list] constructor. +The `luhny_bin()` method takes that list, along with two `0` values that represent the initialized values for the position and the sum. + +The `luhny_bin()` can call itself, which is a behavior called [recursion][recursion]. +Since `luhny_bin()` can call itself, the first thing it does is to check that it is done calling itself. + +```exercism/note +This check is called the terminating condition. +It's critical to have a terminating condition, since every call of a recursive function to itself places another +[frame on the stack](https://realpython.com/lessons/stack-frames-and-stack-traces/#:~:text=A%20stack%20frame%20represents%20a,is%20removed%20from%20the%20stack.). +If there is no terminating condition, then the recursive function will keep calling itself until the stack runs out of space +and a stack overflow error will occur. +``` + +The `luhny_bin()` mtehod should terminate when there are no more characters to process. +By using the [falsiness][falsiness] of an empty list, the [`not` operator][not-operator] can be used instead of comparing the `len()` of the list to `0`. +When all of the characters have been iterated, the function returns if the position is greater than `1` and if the sum is evenly divisible by `10`. + +While there are still characters in the list to iterate, the list is [destructured][destructure] into `head, *tail`. +The [`isdigit()`][isdigit] method is used to see if the head character is a digit. +If so, the [modulo operator][modulo-operator] is used to check if the character's position is evenly divided by `2`. +By using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. +It can be thought of as the expression _not_ having a remainder. + +If the position is evenly divided by `2`, then it is even, and the character is converted to an [`int()`][int] and will be added to the sum variable. + +If the position is odd, then the number is converted to an `int` and is passed to the static method which always doubles it, +and will subtract `9` from the doubled value if the doubled value is greater than `9`. +It does this using a [ternary operator][ternary-operator]. +Inside the ternary operator an [assignment expression][assignment-expression] assigns the doubled value to the `dbl` variable with `(dbl := 2 * num)`. +The ternary operator returns `dbl - 9` if `dbl` is greater than `9`, otherwise it returns `dbl`. +The resulting value will be added to the sum variable. + +Whether the digit is even or odd, the position is added to `1` when it and the sum are passed into the next call of the recursive method. +Also passed in is the tail of the list, which is the list of all the remaining characters after the head character. +Note that the sum and position variables are not being directly changed. +(In other words, they are not being mutated.) +The new sum and position values are calculated as the new arguments to the recursive function. + +If the head character is a space, the recursive function calls itself with the same position and sum values, and the tail. + +If the head character is neither a digit or a space, the function returns `False`. + +[static-method]: https://docs.python.org/3/library/functions.html?#staticmethod +[slicing]: https://www.learnbyexample.org/python-string-slicing/ +[list]: https://docs.python.org/3/library/functions.html?#func-list +[recursion]: https://realpython.com/python-recursion/ +[stack-frame]: https://realpython.com/lessons/stack-frames-and-stack-traces/#:~:text=A%20stack%20frame%20represents%20a,is%20removed%20from%20the%20stack. +[destructure]: https://riptutorial.com/python/example/14981/destructuring-assignment +[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit +[modulo-operator]: https://realpython.com/python-modulo-operator/ +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[not-operator]: https://realpython.com/python-not-operator/ +[int]: https://docs.python.org/3/library/functions.html?#int +[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ +[assignment-expression]: https://peps.python.org/pep-0572/ From fa37586695db8a02f73e3ad2bd4e2c9b72ccd773 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:23:02 -0600 Subject: [PATCH 227/826] Update snippet.txt --- exercises/practice/luhn/.approaches/recursion/snippet.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/luhn/.approaches/recursion/snippet.txt b/exercises/practice/luhn/.approaches/recursion/snippet.txt index c59dd9a5fe..be95f0818e 100644 --- a/exercises/practice/luhn/.approaches/recursion/snippet.txt +++ b/exercises/practice/luhn/.approaches/recursion/snippet.txt @@ -1,6 +1,6 @@ head, *tail = chars if head.isdigit(): - if pos % 2 == 0: + if not pos % 2: return Luhn.luhny_bin(pos + 1, sum + int(head), tail) else: return Luhn.luhny_bin(pos + 1, sum + Luhn.luhny_tune(int(head)), tail) From e29bbeb1cd178d3872c6bb8112ce60801d3dab51 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:24:15 -0600 Subject: [PATCH 228/826] Update content.md --- exercises/practice/luhn/.approaches/reversed-for/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/luhn/.approaches/reversed-for/content.md b/exercises/practice/luhn/.approaches/reversed-for/content.md index 291f5df13a..fcc7a58fe6 100644 --- a/exercises/practice/luhn/.approaches/reversed-for/content.md +++ b/exercises/practice/luhn/.approaches/reversed-for/content.md @@ -38,7 +38,7 @@ That variable is returned from the `valid()` method. The methods that do the work have the [`@staticmethod`][static-method] decorator. This indicates that the method belongs to the class and is not recreated for every object instance. -In the code example the `luhny_bin` method initializes the `total` and `pos` variables to `0`. +In the code example the `luhny_bin()` method initializes the `total` and `pos` variables to `0`. It then calls [`reversed()`][reversed] on the input and iterates the characters from right to left with a [`for`][for] loop. The [`isdigit()`][isdigit] method is used to see if the character is a digit. From 123c50163aa64c0ed466a6217a4700b915946e9e Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:24:41 -0600 Subject: [PATCH 229/826] Update content.md --- .../luhn/.approaches/replace-reverse-enumerate/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md index 8b2a5919d9..812f51909c 100644 --- a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md +++ b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md @@ -37,7 +37,7 @@ That variable is returned from the `valid()` method. The methods that do the work have the [`@staticmethod`][static-method] decorator. This indicates that the method belongs to the class and is not recreated for every object instance. -In the code example the `luhny_bin` method uses the [`replace()`][replace] method to replace all occurrences of a space in the input with an empty string. +In the code example the `luhny_bin()` method uses the [`replace()`][replace] method to replace all occurrences of a space in the input with an empty string. The [`isdigit()`][isdigit] method is then used to see if all of the remaining characters are digits. If not, the function returns `False`. From 78f399be4df4c57636d0eba52ba3558b76cffbab Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:27:23 -0600 Subject: [PATCH 230/826] Update content.md --- .../practice/luhn/.approaches/recursion/content.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/practice/luhn/.approaches/recursion/content.md b/exercises/practice/luhn/.approaches/recursion/content.md index 0c146cbdcd..b6f5f67f7f 100644 --- a/exercises/practice/luhn/.approaches/recursion/content.md +++ b/exercises/practice/luhn/.approaches/recursion/content.md @@ -52,9 +52,9 @@ If there is no terminating condition, then the recursive function will keep call and a stack overflow error will occur. ``` -The `luhny_bin()` mtehod should terminate when there are no more characters to process. +The `luhny_bin()` method should terminate when there are no more characters to process. By using the [falsiness][falsiness] of an empty list, the [`not` operator][not-operator] can be used instead of comparing the `len()` of the list to `0`. -When all of the characters have been iterated, the function returns if the position is greater than `1` and if the sum is evenly divisible by `10`. +When all of the characters have been iterated, the method returns if the position is greater than `1` and if the sum is evenly divisible by `10`. While there are still characters in the list to iterate, the list is [destructured][destructure] into `head, *tail`. The [`isdigit()`][isdigit] method is used to see if the head character is a digit. @@ -75,11 +75,11 @@ Whether the digit is even or odd, the position is added to `1` when it and the s Also passed in is the tail of the list, which is the list of all the remaining characters after the head character. Note that the sum and position variables are not being directly changed. (In other words, they are not being mutated.) -The new sum and position values are calculated as the new arguments to the recursive function. +The new sum and position values are calculated as the new arguments to the recursive method. -If the head character is a space, the recursive function calls itself with the same position and sum values, and the tail. +If the head character is a space, the recursive method calls itself with the same position and sum values, and the tail. -If the head character is neither a digit or a space, the function returns `False`. +If the head character is neither a digit or a space, the method returns `False`. [static-method]: https://docs.python.org/3/library/functions.html?#staticmethod [slicing]: https://www.learnbyexample.org/python-string-slicing/ From 0060e552cbd2531a559a1c28440e6a5b3ee3506c Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:28:51 -0600 Subject: [PATCH 231/826] Update content.md --- .../luhn/.approaches/replace-reverse-enumerate/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md index 812f51909c..fd4e49b12c 100644 --- a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md +++ b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md @@ -39,7 +39,7 @@ This indicates that the method belongs to the class and is not recreated for eve In the code example the `luhny_bin()` method uses the [`replace()`][replace] method to replace all occurrences of a space in the input with an empty string. The [`isdigit()`][isdigit] method is then used to see if all of the remaining characters are digits. -If not, the function returns `False`. +If not, the method returns `False`. [Slicing][slicing] syntax (`[::-1`) is used to reverse the characters in the input, which is then passed into the [`enumerate()`][enumerate] method. The [`for`][for] loop uses `enumerate()` to iterate the reversed characters in the input, returning the character and its position in the string. @@ -57,7 +57,7 @@ Inside the ternary operator an [assignment expression][assignment-expression] as The ternary operator returns `dbl - 9` if `dbl` is greater than `9`, otherwise it returns `dbl`. The resulting value is added to the total variable. -After the iteration of the characters is done, the function return if the position is greater than `1` and if the total is evenly divisible by `10`. +After the iteration of the characters is done, the method returns if the position is greater than `1` and if the total is evenly divisible by `10`. [static-method]: https://docs.python.org/3/library/functions.html?#staticmethod [replace]: https://docs.python.org/3/library/stdtypes.html?#str.replace From 4bbdb16bb5f66e5ab1a4726e2beb543f791aa374 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 07:30:16 -0600 Subject: [PATCH 232/826] Update content.md --- exercises/practice/luhn/.approaches/reversed-for/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/luhn/.approaches/reversed-for/content.md b/exercises/practice/luhn/.approaches/reversed-for/content.md index fcc7a58fe6..abf5a591ca 100644 --- a/exercises/practice/luhn/.approaches/reversed-for/content.md +++ b/exercises/practice/luhn/.approaches/reversed-for/content.md @@ -56,10 +56,10 @@ The ternary operator returns `dbl - 9` if `dbl` is greater than `9`, otherwise i The resulting value is added to the total variable. Whether the digit is even or odd, the position is incremented by `1`. -If the character is not a digit, the the function returns `False` if the character is not a space. +If the character is not a digit, the method returns `False` if the character is not a space. If the character is a space, the loop will go to the next iteration without incrementing the position variable. -After the iteration of the characters is done, the function return if the position is greater than `1` and if the total is evenly divisible by `10`. +After the iteration of the characters is done, the method returns if the position is greater than `1` and if the total is evenly divisible by `10`. [static-method]: https://docs.python.org/3/library/functions.html?#staticmethod [reversed]: https://docs.python.org/3/library/functions.html?#reversed From b43688d439f89128bedb5bf15f3b1abb88328423 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 09:04:14 -0600 Subject: [PATCH 233/826] Create content.md --- exercises/practice/luhn/.articles/content.md | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 exercises/practice/luhn/.articles/content.md diff --git a/exercises/practice/luhn/.articles/content.md b/exercises/practice/luhn/.articles/content.md new file mode 100644 index 0000000000..5069a2e53d --- /dev/null +++ b/exercises/practice/luhn/.articles/content.md @@ -0,0 +1,31 @@ +# Performance + +In this approach, we'll find out how to most efficiently validate a number with the Luhn algorithm. + +The [approaches page][approaches] lists two idiomatic approaches to this exercise: + +1. [Using `reversed()` with a `for` loop][approach-reversed-for] +2. [Using `replace()`, reverse, `enumerate()`][approach-replace-reverse-enumerate] + +For our performance investigation, we'll also include a third approach that [uses recursion][approach-recursion]. + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. + +``` +reversed for: 1.0783263299963438e-05 +replace reverse enumerate: 9.933844099985436e-06 +recursion: 2.4321520800003783e-05 +``` + +At an avergae time per call of `9934` nanoseconds, the `replace()`, reverse, `enumerate()` approach was the fastest. +The `reversed()` with a `for` loop approach was a bit slower, at `10783` nanoseconds. +The recursive approach was much slower, at about `24321` nanoseconds. + +[approaches]: https://exercism.org/tracks/python/exercises/luhn/approaches +[approach-reversed-for]: https://exercism.org/tracks/python/exercises/luhn/approaches/reversed-for +[approach-replace-reverse-enumerate]: https://exercism.org/tracks/python/exercises/luhn/approaches/replace-reverse-enumerate +[approach-recursion]: https://exercism.org/tracks/python/exercises/luhn/approaches/recursion +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/luhn/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html From a5b79c0a016b3e433be82496d1d0d802495b6df0 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 2 Dec 2022 09:05:58 -0600 Subject: [PATCH 234/826] Update introduction.md --- exercises/practice/luhn/.approaches/introduction.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/exercises/practice/luhn/.approaches/introduction.md b/exercises/practice/luhn/.approaches/introduction.md index 904d1e2036..2eff18ae07 100644 --- a/exercises/practice/luhn/.approaches/introduction.md +++ b/exercises/practice/luhn/.approaches/introduction.md @@ -87,6 +87,13 @@ Besides the aforementioned, idiomatic approaches, you could also approach the ex Another approach can use recursion to validate the number. For more information, check the [recursion approach][approach-recursion]. +## Which approach to use? + +The `replace()`, reverse, `enumerate()` approach benchmarked the fastest. + +To compare performance of the approaches, check the [Performance article][article-performance]. + [approach-reversed-for]: https://exercism.org/tracks/python/exercises/luhn/approaches/reversed-for [approach-replace-reverse-enumerate]: https://exercism.org/tracks/python/exercises/luhn/approaches/replace-reverse-enumerate [approach-recursion]: https://exercism.org/tracks/python/exercises/luhn/approaches/recursion +[article-performance]: https://exercism.org/tracks/python/exercises/luhn/articles/performance From b0a957b6cf7d41ff39b1b9f97158e3046b2ac6a7 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Wed, 7 Dec 2022 06:05:49 -0600 Subject: [PATCH 235/826] Rename exercises/practice/luhn/.articles/content.md to exercises/practice/luhn/.articles/performance/content.md --- exercises/practice/luhn/.articles/{ => performance}/content.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exercises/practice/luhn/.articles/{ => performance}/content.md (100%) diff --git a/exercises/practice/luhn/.articles/content.md b/exercises/practice/luhn/.articles/performance/content.md similarity index 100% rename from exercises/practice/luhn/.articles/content.md rename to exercises/practice/luhn/.articles/performance/content.md From de3c41e0deae222c595406ddfa03906a9d200a38 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 2 Dec 2022 14:42:14 -0800 Subject: [PATCH 236/826] Start of linked-list update. --- .../practice/linked-list/.meta/tests.toml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 exercises/practice/linked-list/.meta/tests.toml diff --git a/exercises/practice/linked-list/.meta/tests.toml b/exercises/practice/linked-list/.meta/tests.toml new file mode 100644 index 0000000000..96906d2cc7 --- /dev/null +++ b/exercises/practice/linked-list/.meta/tests.toml @@ -0,0 +1,67 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[7f7e3987-b954-41b8-8084-99beca08752c] +description = "pop gets element from the list" + +[c3f67e5d-cfa2-4c3e-a18f-7ce999c3c885] +description = "push/pop respectively add/remove at the end of the list" + +[00ea24ce-4f5c-4432-abb4-cc6e85462657] +description = "shift gets an element from the list" + +[37962ee0-3324-4a29-b588-5a4c861e6564] +description = "shift gets first element from the list" + +[30a3586b-e9dc-43fb-9a73-2770cec2c718] +description = "unshift adds element at start of the list" + +[042f71e4-a8a7-4cf0-8953-7e4f3a21c42d] +description = "pop, push, shift, and unshift can be used in any order" + +[88f65c0c-4532-4093-8295-2384fb2f37df] +description = "count an empty list" + +[fc055689-5cbe-4cd9-b994-02e2abbb40a5] +description = "count a list with items" + +[8272cef5-130d-40ea-b7f6-5ffd0790d650] +description = "count is correct after mutation" + +[229b8f7a-bd8a-4798-b64f-0dc0bb356d95] +description = "popping to empty doesn't break the list" + +[4e1948b4-514e-424b-a3cf-a1ebbfa2d1ad] +description = "shifting to empty doesn't break the list" + +[e8f7c600-d597-4f79-949d-8ad8bae895a6] +description = "deletes the only element" + +[fd65e422-51f3-45c0-9fd0-c33da638f89b] +description = "deletes the element with the specified value from the list" + +[59db191a-b17f-4ab7-9c5c-60711ec1d013] +description = "deletes the element with the specified value from the list, re-assigns tail" + +[58242222-5d39-415b-951d-8128247f8993] +description = "deletes the element with the specified value from the list, re-assigns head" + +[ee3729ee-3405-4bd2-9bad-de0d4aa5d647] +description = "deletes the first of two elements" + +[47e3b3b4-b82c-4c23-8c1a-ceb9b17cb9fb] +description = "deletes the second of two elements" + +[7b420958-f285-4922-b8f9-10d9dcab5179] +description = "delete does not modify the list if the element is not found" + +[7e04828f-6082-44e3-a059-201c63252a76] +description = "deletes only the first occurrence" From 228f421a13f953f98ec4bd0db2b7aa2e995f6cee Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 18:50:33 +0100 Subject: [PATCH 237/826] Added Jinja template --- .../practice/linked-list/.meta/config.json | 3 +- .../practice/linked-list/.meta/example.py | 20 +- .../practice/linked-list/.meta/template.j2 | 31 +++ .../practice/linked-list/linked_list_test.py | 196 +++++++++++++----- 4 files changed, 189 insertions(+), 61 deletions(-) create mode 100644 exercises/practice/linked-list/.meta/template.j2 diff --git a/exercises/practice/linked-list/.meta/config.json b/exercises/practice/linked-list/.meta/config.json index c2d286f667..894e0a5d3a 100644 --- a/exercises/practice/linked-list/.meta/config.json +++ b/exercises/practice/linked-list/.meta/config.json @@ -9,7 +9,8 @@ "Mofeywalker", "N-Parsons", "pheanex", - "tqa236" + "tqa236", + "meatball" ], "files": { "solution": [ diff --git a/exercises/practice/linked-list/.meta/example.py b/exercises/practice/linked-list/.meta/example.py index 280509f244..997831bd57 100644 --- a/exercises/practice/linked-list/.meta/example.py +++ b/exercises/practice/linked-list/.meta/example.py @@ -54,8 +54,18 @@ def unshift(self, value): def __len__(self): return self.length - def __iter__(self): - current_node = self.head - while current_node: - yield current_node.value - current_node = current_node.succeeding + def delete(self, delete): + node = self.head + while node: + if node.value == delete: + if node.prev: + node.prev.succeeding = node.succeeding + else: + self.head = node.succeeding + if node.succeeding: + node.succeeding.prev = node.prev + else: + self.tail = node.prev + self.length -= 1 + break + node = node.succeeding \ No newline at end of file diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 new file mode 100644 index 0000000000..c765d68a32 --- /dev/null +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -0,0 +1,31 @@ +{%- import "generator_macros.j2" as macros with context -%} +{% macro test_case(case) -%} + {%- set input = case["input"]["operations"] -%} + def test_{{ case["description"] | to_snake }}(self): + lst = LinkedList() + {% for x in input -%} + {%- if "push" == x["operation"] -%} + lst.push({{ x["value"] }}) + {%- elif "pop" == x["operation"] and x["expected"] -%} + self.assertEqual(lst.pop(), {{ x["expected"] }}) + {%- elif "pop" == x["operation"] -%} + lst.pop() + {%- elif "shift" == x["operation"] and x["expected"] -%} + self.assertEqual(lst.shift(), {{ x["expected"] }}) + {%- elif "shift" == x["operation"] -%} + lst.shift() + {%- elif "unshift" == x["operation"] -%} + lst.unshift({{ x["value"] }}) + {%- elif "delete" == x["operation"] -%} + lst.delete({{ x["value"] }}) + {%- elif "count" == x["operation"] -%} + self.assertEqual(len(lst), {{ x["expected"] }}) + {%- endif %} + {% endfor %} +{%- endmacro %} +{{ macros.header(["LinkedList"]) }} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + {{ test_case(case) }} + {% endfor %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 74a5314781..822948b823 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -1,82 +1,168 @@ import unittest -from linked_list import LinkedList +from linked_list import ( + LinkedList, +) + +# Tests adapted from `problem-specifications//canonical-data.json` class LinkedListTest(unittest.TestCase): - def test_push_pop(self): + def test_pop_gets_element_from_the_list(self): + lst = LinkedList() + lst.push(7) + self.assertEqual(lst.pop(), 7) + + def test_push_pop_respectively_add_remove_at_the_end_of_the_list(self): + lst = LinkedList() + lst.push(11) + lst.push(13) + self.assertEqual(lst.pop(), 13) + self.assertEqual(lst.pop(), 11) + + def test_shift_gets_an_element_from_the_list(self): lst = LinkedList() - lst.push(10) - lst.push(20) - self.assertEqual(lst.pop(), 20) - self.assertEqual(lst.pop(), 10) + lst.push(17) + self.assertEqual(lst.shift(), 17) - def test_push_shift(self): + def test_shift_gets_first_element_from_the_list(self): lst = LinkedList() - lst.push(10) - lst.push(20) - self.assertEqual(lst.shift(), 10) - self.assertEqual(lst.shift(), 20) + lst.push(23) + lst.push(5) + self.assertEqual(lst.shift(), 23) + self.assertEqual(lst.shift(), 5) - def test_unshift_shift(self): + def test_unshift_adds_element_at_start_of_the_list(self): lst = LinkedList() - lst.unshift(10) - lst.unshift(20) - self.assertEqual(lst.shift(), 20) - self.assertEqual(lst.shift(), 10) + lst.unshift(23) + lst.unshift(5) + self.assertEqual(lst.shift(), 5) + self.assertEqual(lst.shift(), 23) - def test_unshift_pop(self): + def test_pop_push_shift_and_unshift_can_be_used_in_any_order(self): lst = LinkedList() - lst.unshift(10) - lst.unshift(20) - self.assertEqual(lst.pop(), 10) - self.assertEqual(lst.pop(), 20) + lst.push(1) + lst.push(2) + self.assertEqual(lst.pop(), 2) + lst.push(3) + self.assertEqual(lst.shift(), 1) + lst.unshift(4) + lst.push(5) + self.assertEqual(lst.shift(), 4) + self.assertEqual(lst.pop(), 5) + self.assertEqual(lst.shift(), 3) - def test_all(self): + def test_count_an_empty_list(self): lst = LinkedList() - lst.push(10) - lst.push(20) - self.assertEqual(lst.pop(), 20) - lst.push(30) - self.assertEqual(lst.shift(), 10) - lst.unshift(40) - lst.push(50) - self.assertEqual(lst.shift(), 40) - self.assertEqual(lst.pop(), 50) - self.assertEqual(lst.shift(), 30) + self.assertEqual(len(lst), 0) - @unittest.skip("extra-credit") - def test_length(self): + def test_count_a_list_with_items(self): lst = LinkedList() - lst.push(10) - lst.push(20) + lst.push(37) + lst.push(1) + self.assertEqual(len(lst), 2) + + def test_count_is_correct_after_mutation(self): + lst = LinkedList() + lst.push(31) + self.assertEqual(len(lst), 1) + lst.unshift(43) self.assertEqual(len(lst), 2) lst.shift() self.assertEqual(len(lst), 1) lst.pop() self.assertEqual(len(lst), 0) - @unittest.skip("extra-credit") - def test_iterator(self): + def test_popping_to_empty_doesn_t_break_the_list(self): lst = LinkedList() - lst.push(10) - lst.push(20) - iterator = iter(lst) - self.assertEqual(next(iterator), 10) - self.assertEqual(next(iterator), 20) + lst.push(41) + lst.push(59) + lst.pop() + lst.pop() + lst.push(47) + self.assertEqual(len(lst), 1) + self.assertEqual(lst.pop(), 47) - @unittest.skip("extra-credit") - def test_iterator_independence(self): + def test_shifting_to_empty_doesn_t_break_the_list(self): lst = LinkedList() - lst.push(10) - lst.push(20) - iterator_a = iter(lst) - iterator_b = iter(lst) - self.assertEqual(next(iterator_a), 10) - self.assertEqual(next(iterator_a), 20) - self.assertEqual(next(iterator_b), 10) - self.assertEqual(next(iterator_b), 20) + lst.push(41) + lst.push(59) + lst.shift() + lst.shift() + lst.push(47) + self.assertEqual(len(lst), 1) + self.assertEqual(lst.shift(), 47) + def test_deletes_the_only_element(self): + lst = LinkedList() + lst.push(61) + lst.delete(61) + self.assertEqual(len(lst), 0) -if __name__ == '__main__': - unittest.main() + def test_deletes_the_element_with_the_specified_value_from_the_list(self): + lst = LinkedList() + lst.push(71) + lst.push(83) + lst.push(79) + lst.delete(83) + self.assertEqual(len(lst), 2) + self.assertEqual(lst.pop(), 79) + self.assertEqual(lst.shift(), 71) + + def test_deletes_the_element_with_the_specified_value_from_the_list_re_assigns_tail( + self, + ): + lst = LinkedList() + lst.push(71) + lst.push(83) + lst.push(79) + lst.delete(83) + self.assertEqual(len(lst), 2) + self.assertEqual(lst.pop(), 79) + self.assertEqual(lst.pop(), 71) + + def test_deletes_the_element_with_the_specified_value_from_the_list_re_assigns_head( + self, + ): + lst = LinkedList() + lst.push(71) + lst.push(83) + lst.push(79) + lst.delete(83) + self.assertEqual(len(lst), 2) + self.assertEqual(lst.shift(), 71) + self.assertEqual(lst.shift(), 79) + + def test_deletes_the_first_of_two_elements(self): + lst = LinkedList() + lst.push(97) + lst.push(101) + lst.delete(97) + self.assertEqual(len(lst), 1) + self.assertEqual(lst.pop(), 101) + + def test_deletes_the_second_of_two_elements(self): + lst = LinkedList() + lst.push(97) + lst.push(101) + lst.delete(101) + self.assertEqual(len(lst), 1) + self.assertEqual(lst.pop(), 97) + + def test_delete_does_not_modify_the_list_if_the_element_is_not_found(self): + lst = LinkedList() + lst.push(89) + lst.delete(103) + self.assertEqual(len(lst), 1) + + def test_deletes_only_the_first_occurrence(self): + lst = LinkedList() + lst.push(73) + lst.push(9) + lst.push(9) + lst.push(107) + lst.delete(9) + self.assertEqual(len(lst), 3) + self.assertEqual(lst.pop(), 107) + self.assertEqual(lst.pop(), 9) + self.assertEqual(lst.pop(), 73) From 2aa781a4974b9ed7d769a89725dba970758fa868 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 18:51:25 +0100 Subject: [PATCH 238/826] Fix --- exercises/practice/linked-list/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/linked-list/.meta/config.json b/exercises/practice/linked-list/.meta/config.json index 894e0a5d3a..3c1731c0be 100644 --- a/exercises/practice/linked-list/.meta/config.json +++ b/exercises/practice/linked-list/.meta/config.json @@ -10,7 +10,7 @@ "N-Parsons", "pheanex", "tqa236", - "meatball" + "meatball133" ], "files": { "solution": [ From 2ff1eebd13a741dd6098c9e893b234d0d8d0d2f4 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 3 Dec 2022 10:51:44 -0800 Subject: [PATCH 239/826] Create instructions.apped.md Rough outline of instructions.append --- .../linked-list/.docs/instructions.apped.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 exercises/practice/linked-list/.docs/instructions.apped.md diff --git a/exercises/practice/linked-list/.docs/instructions.apped.md b/exercises/practice/linked-list/.docs/instructions.apped.md new file mode 100644 index 0000000000..7b649b857b --- /dev/null +++ b/exercises/practice/linked-list/.docs/instructions.apped.md @@ -0,0 +1,42 @@ +# Instructions append + +## How this Exercise is Structured in Python + +While linked lists can be implemented in a variety of ways with a variety of underlying data structures, we ask here that you implement your linked list in an OOP fashion. + +In the stub file, you will see a `Node` class and a `LinkedList` class. +Your `Node` class should keep track of its value, as well as which other nodes preceed or follow. +Your `push`, `pop`, `shift`, `unshift`, and the special method for `len` should be implemented in the `LinkedList` class. + + +In addition to the methods outlined above, we also ask that you implement `delete`. +`delete` will take one argument, which is the vaule to be removed from the linked list. +If the value appears more than once, the **first** occurance should be removed. + +
+ +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when a node value being `delet()`-ed is not found in the linked list. Additionally, an `IndexError` should be thrown if there are no nodes left to `pop()`. The tests will only pass if you both `raise` these `exceptions` and include messages with them. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# When the value passed to `delete()` is not found. + + +# When pop() is called and there are no nodes left in the linked list + + +``` + +## Special Methods in Python + +The tests for this exercise will also be calling `len()` on your `LinkedList`. +In order for `len()` to work, you will need to create a `__len__` special method. +For details on implementing special or "dunder" methods in Python, see [Python Docs: Basic Object Customization][basic customization] and [Python Docs: object.__len__(self)][__len__]. + +
+ From 9c90b83909e5af366d43d4044e76b0518ed81483 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 20:20:36 +0100 Subject: [PATCH 240/826] progress --- .../linked-list/.meta/additional_tests.json | 14 ++++++++++++++ exercises/practice/linked-list/.meta/template.j2 | 15 +++++++++++++++ .../practice/linked-list/linked_list_test.py | 6 ++++++ 3 files changed, 35 insertions(+) create mode 100644 exercises/practice/linked-list/.meta/additional_tests.json diff --git a/exercises/practice/linked-list/.meta/additional_tests.json b/exercises/practice/linked-list/.meta/additional_tests.json new file mode 100644 index 0000000000..a18e57356d --- /dev/null +++ b/exercises/practice/linked-list/.meta/additional_tests.json @@ -0,0 +1,14 @@ +{ + "cases": [ + { + "description": "Hi", + "property": "list", + "input": { + "operations": [ + { + "operation": "pop" + } + ] + }, "expected": {"error": "List is empty"} + } +]} \ No newline at end of file diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index c765d68a32..31eed581c3 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -1,9 +1,11 @@ {%- import "generator_macros.j2" as macros with context -%} {% macro test_case(case) -%} {%- set input = case["input"]["operations"] -%} + {%- set last = "" -%} def test_{{ case["description"] | to_snake }}(self): lst = LinkedList() {% for x in input -%} + {%- set last = x["operation"] -%} {%- if "push" == x["operation"] -%} lst.push({{ x["value"] }}) {%- elif "pop" == x["operation"] and x["expected"] -%} @@ -22,6 +24,11 @@ self.assertEqual(len(lst), {{ x["expected"] }}) {%- endif %} {% endfor %} + + {% if case["expected"] and last == "pop" %} + hi + {% endif %} + {%- endmacro %} {{ macros.header(["LinkedList"]) }} @@ -29,3 +36,11 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} {{ test_case(case) }} {% endfor %} + {% if additional_cases | length -%} + + # Additional tests for this track + + {% for case in additional_cases -%} + {{ test_case(case) }} + {% endfor %} + {%- endif %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 822948b823..e6bc7270d7 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -166,3 +166,9 @@ def test_deletes_only_the_first_occurrence(self): self.assertEqual(lst.pop(), 107) self.assertEqual(lst.pop(), 9) self.assertEqual(lst.pop(), 73) + + # Additional tests for this track + + def test_hi(self): + lst = LinkedList() + lst.pop() From fc4c03b45d797c19fb8b192560da1ba0199ab92b Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 20:29:50 +0100 Subject: [PATCH 241/826] more stuff --- exercises/practice/linked-list/.meta/template.j2 | 7 +++---- exercises/practice/linked-list/linked_list_test.py | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 31eed581c3..4a9ec4b932 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -1,11 +1,10 @@ {%- import "generator_macros.j2" as macros with context -%} {% macro test_case(case) -%} {%- set input = case["input"]["operations"] -%} - {%- set last = "" -%} def test_{{ case["description"] | to_snake }}(self): lst = LinkedList() {% for x in input -%} - {%- set last = x["operation"] -%} + {% set last = x["operation"] %} {%- if "push" == x["operation"] -%} lst.push({{ x["value"] }}) {%- elif "pop" == x["operation"] and x["expected"] -%} @@ -25,8 +24,8 @@ {%- endif %} {% endfor %} - {% if case["expected"] and last == "pop" %} - hi + {% if case["expected"] and input[-1]["operation"] == "pop" %} + {% endif %} {%- endmacro %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index e6bc7270d7..1c58d4995c 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -172,3 +172,5 @@ def test_deletes_only_the_first_occurrence(self): def test_hi(self): lst = LinkedList() lst.pop() + + hi From b85fc64b53c4f988efac1110e5925c7bf60838f9 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 3 Dec 2022 21:16:23 +0100 Subject: [PATCH 242/826] progress --- .../linked-list/.meta/additional_tests.json | 11 ++-- .../practice/linked-list/.meta/template.j2 | 50 +++++++++++-------- .../practice/linked-list/linked_list_test.py | 8 +-- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/exercises/practice/linked-list/.meta/additional_tests.json b/exercises/practice/linked-list/.meta/additional_tests.json index a18e57356d..5a31665473 100644 --- a/exercises/practice/linked-list/.meta/additional_tests.json +++ b/exercises/practice/linked-list/.meta/additional_tests.json @@ -5,9 +5,14 @@ "property": "list", "input": { "operations": [ - { - "operation": "pop" - } + { + "operation": "push", + "value": 1 + }, + { + "operation": "pop" + } + ] }, "expected": {"error": "List is empty"} } diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 4a9ec4b932..9fe03b776d 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -1,31 +1,39 @@ {%- import "generator_macros.j2" as macros with context -%} {% macro test_case(case) -%} - {%- set input = case["input"]["operations"] -%} def test_{{ case["description"] | to_snake }}(self): lst = LinkedList() - {% for x in input -%} - {% set last = x["operation"] %} - {%- if "push" == x["operation"] -%} - lst.push({{ x["value"] }}) - {%- elif "pop" == x["operation"] and x["expected"] -%} - self.assertEqual(lst.pop(), {{ x["expected"] }}) - {%- elif "pop" == x["operation"] -%} + {%- set inputs = case["input"]["operations"] -%} + {%- if case["expected"] and inputs|length > 1 -%} + {%- set last = inputs[-1]["operation"] -%} + {% set inputs = inputs[:-1] %} + {%- elif case["expected"] -%} + {% set last = inputs[-1]["operation"] %} + {% set inputs = [{"operation": "None"}] %} + {%- endif %} + {% for input in inputs -%} + {%- if "push" == input["operation"] -%} + lst.push({{ input["value"] }}) + {%- elif "pop" == input["operation"] and input["expected"] -%} + self.assertEqual(lst.pop(), {{ input["expected"] }}) + {%- elif "pop" == input["operation"] -%} lst.pop() - {%- elif "shift" == x["operation"] and x["expected"] -%} - self.assertEqual(lst.shift(), {{ x["expected"] }}) - {%- elif "shift" == x["operation"] -%} + {%- elif "shift" == input["operation"] and input["expected"] -%} + self.assertEqual(lst.shift(), {{ input["expected"] }}) + {%- elif "shift" == input["operation"] -%} lst.shift() - {%- elif "unshift" == x["operation"] -%} - lst.unshift({{ x["value"] }}) - {%- elif "delete" == x["operation"] -%} - lst.delete({{ x["value"] }}) - {%- elif "count" == x["operation"] -%} - self.assertEqual(len(lst), {{ x["expected"] }}) + {%- elif "unshift" == input["operation"] -%} + lst.unshift({{ input["value"] }}) + {%- elif "delete" == input["operation"] -%} + lst.delete({{ input["value"] }}) + {%- elif "count" == input["operation"] -%} + self.assertEqual(len(lst), {{ input["expected"] }}) {%- endif %} - {% endfor %} - - {% if case["expected"] and input[-1]["operation"] == "pop" %} - + {% endfor -%} + {%- if case["expected"] and last -%} + with self.assertRaises(ValueError) as err: + lst.pop() + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "{{case["expected"]["error"]}}") {% endif %} {%- endmacro %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 1c58d4995c..c1eeaca35a 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -171,6 +171,8 @@ def test_deletes_only_the_first_occurrence(self): def test_hi(self): lst = LinkedList() - lst.pop() - - hi + lst.push(1) + with self.assertRaises(ValueError) as err: + lst.pop() + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "List is empty") From 2d413b1c6d99151acfeae420ed9f7bd7254c3e09 Mon Sep 17 00:00:00 2001 From: Meatball Date: Tue, 6 Dec 2022 08:57:15 +0100 Subject: [PATCH 243/826] Updated the test file --- .../linked-list/.meta/additional_tests.json | 106 +++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/exercises/practice/linked-list/.meta/additional_tests.json b/exercises/practice/linked-list/.meta/additional_tests.json index 5a31665473..ce01e33972 100644 --- a/exercises/practice/linked-list/.meta/additional_tests.json +++ b/exercises/practice/linked-list/.meta/additional_tests.json @@ -1,7 +1,19 @@ { "cases": [ { - "description": "Hi", + "description": "Using pop raises an error if the list is empty", + "property": "list", + "input": { + "operations": [ + { + "operation": "pop" + } + + ] + }, "expected": {"error": "List is empty"} + }, + { + "description": "Can return with pop and then raise an error if empty", "property": "list", "input": { "operations": [ @@ -9,11 +21,103 @@ "operation": "push", "value": 1 }, + { + "operation": "unshift", + "value": 5 + }, + { + "operation": "pop", + "expected": 5 + }, + { + "operation": "pop", + "expected": 1 + }, { "operation": "pop" } ] }, "expected": {"error": "List is empty"} + }, + { + "description": "Using shift raises an error if the list is empty", + "property": "list", + "input": { + "operations": [ + { + "operation": "shift" + } + + ] + }, "expected": {"error": "List is empty"} + }, + { + "description": "Can return with shift and then raise an error if empty", + "property": "list", + "input": { + "operations": [ + { + "operation": "push", + "value": 1 + }, + { + "operation": "unshift", + "value": 5 + }, + { + "operation": "pop", + "expected": 5 + }, + { + "operation": "shift", + "expected": 1 + }, + { + "operation": "shift" + } + + ] + }, "expected": {"error": "List is empty"} + }, + { + "description": "Using delete raises an error if the list is empty", + "property": "list", + "input": { + "operations": [ + { + "operation": "delete", + "value": 0 + } + + ] + }, "expected": {"error": "Value not found"} + }, + { + "description": "Using delete raises an error if the it is not found", + "property": "list", + "input": { + "operations": [ + { + "operation": "push", + "value": 5 + }, + { + "operation": "push", + "value": 7 + }, + { + "operation": "pop", + "expected": 7 + }, + { + "operation": "delete", + "value": 7 + } + + ] + }, "expected": {"error": "Value not found"} } + + ]} \ No newline at end of file From 7b46c1c302648784c9417fe09b274d88e107326f Mon Sep 17 00:00:00 2001 From: Meatball Date: Tue, 6 Dec 2022 08:58:35 +0100 Subject: [PATCH 244/826] fix --- exercises/practice/linked-list/.meta/additional_tests.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/exercises/practice/linked-list/.meta/additional_tests.json b/exercises/practice/linked-list/.meta/additional_tests.json index ce01e33972..25d8ef0776 100644 --- a/exercises/practice/linked-list/.meta/additional_tests.json +++ b/exercises/practice/linked-list/.meta/additional_tests.json @@ -118,6 +118,4 @@ ] }, "expected": {"error": "Value not found"} } - - ]} \ No newline at end of file From 95b83515d79ff11f1f62432b4eca81beb8e77659 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 6 Dec 2022 00:01:05 -0800 Subject: [PATCH 245/826] Update template.j2 Reworked template. --- .../practice/linked-list/.meta/template.j2 | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 9fe03b776d..bbbada690d 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -11,22 +11,21 @@ {% set inputs = [{"operation": "None"}] %} {%- endif %} {% for input in inputs -%} - {%- if "push" == input["operation"] -%} - lst.push({{ input["value"] }}) - {%- elif "pop" == input["operation"] and input["expected"] -%} - self.assertEqual(lst.pop(), {{ input["expected"] }}) - {%- elif "pop" == input["operation"] -%} - lst.pop() - {%- elif "shift" == input["operation"] and input["expected"] -%} - self.assertEqual(lst.shift(), {{ input["expected"] }}) - {%- elif "shift" == input["operation"] -%} - lst.shift() - {%- elif "unshift" == input["operation"] -%} - lst.unshift({{ input["value"] }}) - {%- elif "delete" == input["operation"] -%} - lst.delete({{ input["value"] }}) - {%- elif "count" == input["operation"] -%} - self.assertEqual(len(lst), {{ input["expected"] }}) + {%- set operation = input["operation"] -%} + {%- set value = input["value"] -%} + {%- set expected = input["expected"] -%} + {%- if operation and value -%} + lst.{{ operation }}({{ value }}) + {%- elif operation and expected -%} + {%- if operation == "count" -%} + self.assertEqual(len(lst), {{ expected }}) + {%- else -%} + self.assertEqual(lst.{{ operation }}(), {{ expected }}) + {%- endif -%} + {%- elif operation == "count" -%} + self.assertEqual(len(lst), {{ expected }}) + {%- else -%} + lst.{{ operation }}() {%- endif %} {% endfor -%} {%- if case["expected"] and last -%} From d9255250a986b3b59dcf2e6755ad9f66048ef37b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 6 Dec 2022 01:29:45 -0800 Subject: [PATCH 246/826] JinJa Redo Number Two --- .../practice/linked-list/.meta/template.j2 | 42 +++++++++---------- .../practice/linked-list/linked_list_test.py | 10 ----- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index bbbada690d..08294ae78f 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -5,34 +5,34 @@ {%- set inputs = case["input"]["operations"] -%} {%- if case["expected"] and inputs|length > 1 -%} {%- set last = inputs[-1]["operation"] -%} - {% set inputs = inputs[:-1] %} + {%- set inputs = inputs[:-1] -%} {%- elif case["expected"] -%} - {% set last = inputs[-1]["operation"] %} - {% set inputs = [{"operation": "None"}] %} - {%- endif %} + {%- set last = inputs[-1]["operation"] -%} + {%- set inputs = [{"operation": "None"}] -%} + {%- endif -%} {% for input in inputs -%} {%- set operation = input["operation"] -%} {%- set value = input["value"] -%} - {%- set expected = input["expected"] -%} - {%- if operation and value -%} - lst.{{ operation }}({{ value }}) - {%- elif operation and expected -%} - {%- if operation == "count" -%} - self.assertEqual(len(lst), {{ expected }}) - {%- else -%} - self.assertEqual(lst.{{ operation }}(), {{ expected }}) - {%- endif -%} - {%- elif operation == "count" -%} - self.assertEqual(len(lst), {{ expected }}) - {%- else -%} - lst.{{ operation }}() + {%- set expected = input["expected"] %} + {%- if operation and value %} + lst.{{ operation }}({{ value }}) + {%- elif operation and expected %} + {%- if operation == "count" %} + self.assertEqual(len(lst), {{ expected }}) + {%- else %} + self.assertEqual(lst.{{ operation }}(), {{ expected }}) + {%- endif %} + {%- elif operation == "count" %} + self.assertEqual(len(lst), {{ expected }}) + {%- else %} + lst.{{ operation }}() {%- endif %} - {% endfor -%} - {%- if case["expected"] and last -%} + {%- endfor %} + {% if case["expected"] and last %} with self.assertRaises(ValueError) as err: lst.pop() - self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "{{case["expected"]["error"]}}") + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "{{case["expected"]["error"]}}") {% endif %} {%- endmacro %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index c1eeaca35a..822948b823 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -166,13 +166,3 @@ def test_deletes_only_the_first_occurrence(self): self.assertEqual(lst.pop(), 107) self.assertEqual(lst.pop(), 9) self.assertEqual(lst.pop(), 73) - - # Additional tests for this track - - def test_hi(self): - lst = LinkedList() - lst.push(1) - with self.assertRaises(ValueError) as err: - lst.pop() - self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "List is empty") From cedb63b9d224d552b0d10ffb6e1bada489e26f7f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 7 Dec 2022 00:53:00 -0800 Subject: [PATCH 247/826] Updated JinJa Template Updated template Re-generated test cases Re-formatted json file --- .../linked-list/.meta/additional_tests.json | 63 +++++++++++-------- .../practice/linked-list/.meta/template.j2 | 38 +++++++---- .../practice/linked-list/linked_list_test.py | 60 ++++++++++++++++-- 3 files changed, 116 insertions(+), 45 deletions(-) diff --git a/exercises/practice/linked-list/.meta/additional_tests.json b/exercises/practice/linked-list/.meta/additional_tests.json index 25d8ef0776..2b28b6aa0e 100644 --- a/exercises/practice/linked-list/.meta/additional_tests.json +++ b/exercises/practice/linked-list/.meta/additional_tests.json @@ -1,6 +1,7 @@ { "cases": [ { + "uuid": "c472bec8-45b5-477f-96c1-3bed58ca0b1a", "description": "Using pop raises an error if the list is empty", "property": "list", "input": { @@ -13,34 +14,36 @@ }, "expected": {"error": "List is empty"} }, { - "description": "Can return with pop and then raise an error if empty", + "uuid": "394a61d3-7a69-4153-9eb7-f8d8e23cf6f0", + "description" : "Can return with pop and then raise an error if empty", "property": "list", "input": { - "operations": [ - { - "operation": "push", - "value": 1 - }, - { - "operation": "unshift", - "value": 5 - }, - { - "operation": "pop", - "expected": 5 - }, - { - "operation": "pop", - "expected": 1 - }, - { - "operation": "pop" - } - - ] - }, "expected": {"error": "List is empty"} + "operations": [ + { + "operation": "push", + "value": 1 + }, + { + "operation": "unshift", + "value": 5 + }, + { + "operation": "pop", + "expected": 5 + }, + { + "operation": "pop", + "expected": 1 + }, + { + "operation": "pop" + } + ] + }, + "expected": {"error": "List is empty"} }, { + "uuid": "6d1a1817-0d4c-4953-abfd-c5cd5639871f", "description": "Using shift raises an error if the list is empty", "property": "list", "input": { @@ -50,9 +53,11 @@ } ] - }, "expected": {"error": "List is empty"} + }, + "expected": {"error": "List is empty"} }, { + "uuid": "cae5d135-f948-44ce-8940-c9b898b95756", "description": "Can return with shift and then raise an error if empty", "property": "list", "input": { @@ -78,9 +83,11 @@ } ] - }, "expected": {"error": "List is empty"} + }, + "expected": {"error": "List is empty"} }, { + "uuid": "1fa81a88-9af7-49a1-a4b1-b1aa641bfcbb", "description": "Using delete raises an error if the list is empty", "property": "list", "input": { @@ -91,9 +98,11 @@ } ] - }, "expected": {"error": "Value not found"} + }, + "expected": {"error": "Value not found"} }, { + "uuid": "f7f9d2dc-be1b-4f42-be2e-c29980ce0823", "description": "Using delete raises an error if the it is not found", "property": "list", "input": { diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 08294ae78f..0659aff2d8 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -4,10 +4,15 @@ lst = LinkedList() {%- set inputs = case["input"]["operations"] -%} {%- if case["expected"] and inputs|length > 1 -%} - {%- set last = inputs[-1]["operation"] -%} + {%- set final = inputs[-1]["operation"] -%} {%- set inputs = inputs[:-1] -%} - {%- elif case["expected"] -%} - {%- set last = inputs[-1]["operation"] -%} + {% if case["expected"]["error"] %} + {%- set error_case = true %} + {%- set error_msg = case["expected"]["error"] %} + {%- set error_operation = inputs[-1]["operation"] %} + {%- endif %} + {%- elif case["expected"] and not error_case -%} + {%- set final = inputs[-1]["operation"] -%} {%- set inputs = [{"operation": "None"}] -%} {%- endif -%} {% for input in inputs -%} @@ -24,18 +29,28 @@ {%- endif %} {%- elif operation == "count" %} self.assertEqual(len(lst), {{ expected }}) - {%- else %} - lst.{{ operation }}() + {%- elif case["expected"]["error"] %} + with self.assertRaises(IndexError) as err: + lst.{{ case["input"]["operations"][-1]["operation"] }}() + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") {%- endif %} {%- endfor %} - {% if case["expected"] and last %} - with self.assertRaises(ValueError) as err: - lst.pop() + {%- if error_case %} + {%- if final == "pop" or final == "shift" %} + with self.assertRaises(IndexError) as err: + lst.{{ error_operation }}() self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "{{case["expected"]["error"]}}") - {% endif %} - + self.assertEqual(err.exception.args[0], "{{ error_msg }}") + {% elif final == "delete" %} + with self.assertRaises(ValueError) as err: + lst.{{ error_operation }}() + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ error_msg }}") + {%- endif %} + {%- endif %} {%- endmacro %} + {{ macros.header(["LinkedList"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): @@ -45,7 +60,6 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% if additional_cases | length -%} # Additional tests for this track - {% for case in additional_cases -%} {{ test_case(case) }} {% endfor %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 822948b823..97cab20bab 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -68,17 +68,13 @@ def test_count_is_correct_after_mutation(self): self.assertEqual(len(lst), 1) lst.unshift(43) self.assertEqual(len(lst), 2) - lst.shift() self.assertEqual(len(lst), 1) - lst.pop() self.assertEqual(len(lst), 0) def test_popping_to_empty_doesn_t_break_the_list(self): lst = LinkedList() lst.push(41) lst.push(59) - lst.pop() - lst.pop() lst.push(47) self.assertEqual(len(lst), 1) self.assertEqual(lst.pop(), 47) @@ -87,8 +83,6 @@ def test_shifting_to_empty_doesn_t_break_the_list(self): lst = LinkedList() lst.push(41) lst.push(59) - lst.shift() - lst.shift() lst.push(47) self.assertEqual(len(lst), 1) self.assertEqual(lst.shift(), 47) @@ -166,3 +160,57 @@ def test_deletes_only_the_first_occurrence(self): self.assertEqual(lst.pop(), 107) self.assertEqual(lst.pop(), 9) self.assertEqual(lst.pop(), 73) + + # Additional tests for this track + def test_using_pop_raises_an_error_if_the_list_is_empty(self): + lst = LinkedList() + with self.assertRaises(IndexError) as err: + lst.pop() + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "List is empty") + + def test_can_return_with_pop_and_then_raise_an_error_if_empty(self): + lst = LinkedList() + lst.push(1) + lst.unshift(5) + self.assertEqual(lst.pop(), 5) + self.assertEqual(lst.pop(), 1) + with self.assertRaises(IndexError) as err: + lst.pop() + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "List is empty") + + def test_using_shift_raises_an_error_if_the_list_is_empty(self): + lst = LinkedList() + with self.assertRaises(IndexError) as err: + lst.shift() + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "List is empty") + + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + lst = LinkedList() + lst.push(1) + lst.unshift(5) + self.assertEqual(lst.pop(), 5) + self.assertEqual(lst.shift(), 1) + with self.assertRaises(IndexError) as err: + lst.shift() + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "List is empty") + + def test_using_delete_raises_an_error_if_the_list_is_empty(self): + lst = LinkedList() + with self.assertRaises(IndexError) as err: + lst.delete() + self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "Value not found") + + def test_using_delete_raises_an_error_if_the_it_is_not_found(self): + lst = LinkedList() + lst.push(5) + lst.push(7) + self.assertEqual(lst.pop(), 7) + with self.assertRaises(ValueError) as err: + lst.pop() + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "Value not found") From ce16d819b9a92c4db9d9401255fda4b2ea90e361 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 7 Dec 2022 01:48:44 -0800 Subject: [PATCH 248/826] Template Correction --- exercises/practice/linked-list/.meta/template.j2 | 7 +++++++ exercises/practice/linked-list/linked_list_test.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 0659aff2d8..4760945d65 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -30,11 +30,18 @@ {%- elif operation == "count" %} self.assertEqual(len(lst), {{ expected }}) {%- elif case["expected"]["error"] %} + {%- if final == "pop" or final == "shift" %} with self.assertRaises(IndexError) as err: lst.{{ case["input"]["operations"][-1]["operation"] }}() self.assertEqual(type(err.exception), IndexError) + self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") + {% elif final == "delete" %} + with self.assertRaises(ValueError) as err: + lst.{{ case["input"]["operations"][-1]["operation"] }}() + self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") {%- endif %} + {%- endif %} {%- endfor %} {%- if error_case %} {%- if final == "pop" or final == "shift" %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 97cab20bab..0080921a2f 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -200,9 +200,9 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): def test_using_delete_raises_an_error_if_the_list_is_empty(self): lst = LinkedList() - with self.assertRaises(IndexError) as err: + with self.assertRaises(ValueError) as err: lst.delete() - self.assertEqual(type(err.exception), IndexError) + self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "Value not found") def test_using_delete_raises_an_error_if_the_it_is_not_found(self): From cfdf420f94393bb0685cd5ac75fcc64244161039 Mon Sep 17 00:00:00 2001 From: Meatball Date: Wed, 7 Dec 2022 16:35:09 +0100 Subject: [PATCH 249/826] Updated example and tests --- .../practice/linked-list/.meta/additional_tests.json | 8 ++++---- exercises/practice/linked-list/.meta/example.py | 10 +++++++++- exercises/practice/linked-list/.meta/tests.toml | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/exercises/practice/linked-list/.meta/additional_tests.json b/exercises/practice/linked-list/.meta/additional_tests.json index 2b28b6aa0e..5be9653f3f 100644 --- a/exercises/practice/linked-list/.meta/additional_tests.json +++ b/exercises/practice/linked-list/.meta/additional_tests.json @@ -29,11 +29,11 @@ }, { "operation": "pop", - "expected": 5 + "expected": 1 }, { "operation": "pop", - "expected": 1 + "expected": 5 }, { "operation": "pop" @@ -72,11 +72,11 @@ }, { "operation": "pop", - "expected": 5 + "expected": 1 }, { "operation": "shift", - "expected": 1 + "expected": 5 }, { "operation": "shift" diff --git a/exercises/practice/linked-list/.meta/example.py b/exercises/practice/linked-list/.meta/example.py index 997831bd57..536bd2491c 100644 --- a/exercises/practice/linked-list/.meta/example.py +++ b/exercises/practice/linked-list/.meta/example.py @@ -23,6 +23,8 @@ def push(self, value): def pop(self): node = self.tail + if self.length == 0: + raise IndexError("List is empty") if node is None or node.prev is None: self.head = self.tail = None else: @@ -32,6 +34,8 @@ def pop(self): return node.value def shift(self): + if self.length == 0: + raise IndexError("List is empty") node = self.head if node is None or node.succeeding is None: self.head = self.tail = None @@ -55,6 +59,7 @@ def __len__(self): return self.length def delete(self, delete): + found = False node = self.head while node: if node.value == delete: @@ -66,6 +71,9 @@ def delete(self, delete): node.succeeding.prev = node.prev else: self.tail = node.prev + found = True self.length -= 1 break - node = node.succeeding \ No newline at end of file + node = node.succeeding + if not found: + raise ValueError("Value not found") \ No newline at end of file diff --git a/exercises/practice/linked-list/.meta/tests.toml b/exercises/practice/linked-list/.meta/tests.toml index 96906d2cc7..c5862ff4c4 100644 --- a/exercises/practice/linked-list/.meta/tests.toml +++ b/exercises/practice/linked-list/.meta/tests.toml @@ -62,6 +62,7 @@ description = "deletes the second of two elements" [7b420958-f285-4922-b8f9-10d9dcab5179] description = "delete does not modify the list if the element is not found" +include = false [7e04828f-6082-44e3-a059-201c63252a76] description = "deletes only the first occurrence" From 75ba3565c0009bbdecba8a51b47149ef2da5f0c1 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 8 Dec 2022 01:32:12 -0800 Subject: [PATCH 250/826] More LL Edits Final (I hope) touchups on template Adjustments to example solution Re-Generated test files. --- .../practice/linked-list/.meta/example.py | 65 ++++++++++++------- .../practice/linked-list/.meta/template.j2 | 30 +++++---- .../practice/linked-list/linked_list_test.py | 22 +++---- 3 files changed, 70 insertions(+), 47 deletions(-) diff --git a/exercises/practice/linked-list/.meta/example.py b/exercises/practice/linked-list/.meta/example.py index 536bd2491c..1496ef2596 100644 --- a/exercises/practice/linked-list/.meta/example.py +++ b/exercises/practice/linked-list/.meta/example.py @@ -11,6 +11,41 @@ def __init__(self): self.tail = None self.length = 0 + def __len__(self): + return self.length + + def __iter__(self): + current_node = self.head + while current_node: + yield (current_node.value, current_node.succeeding, current_node.prev) + current_node = current_node.succeeding + + def delete(self, to_delete): + if self.length == 0: + raise ValueError("Value not found") + found = False + node = self.head + + for value, succeeding, prev in self: + if value == to_delete: + if prev: + node.prev.succeeding = succeeding + else: + self.head = succeeding + if succeeding: + node.succeeding.prev = prev + else: + self.tail = prev + + found = True + self.length -= 1 + break + + node = node.succeeding + + if not found: + raise ValueError("Value not found") + def push(self, value): new_node = Node(value) if not self.head: @@ -19,10 +54,12 @@ def push(self, value): new_node.prev = self.tail self.tail.succeeding = new_node self.tail = new_node + self.length += 1 def pop(self): node = self.tail + if self.length == 0: raise IndexError("List is empty") if node is None or node.prev is None: @@ -30,7 +67,10 @@ def pop(self): else: self.tail = self.tail.prev self.tail.succeeding = None + self.length -= 1 + print(self.length) + return node.value def shift(self): @@ -43,6 +83,7 @@ def shift(self): self.head = self.head.succeeding self.head.prev = None self.length -= 1 + return node.value def unshift(self, value): @@ -53,27 +94,5 @@ def unshift(self, value): new_node.succeeding = self.head self.head.prev = new_node self.head = new_node - self.length += 1 - def __len__(self): - return self.length - - def delete(self, delete): - found = False - node = self.head - while node: - if node.value == delete: - if node.prev: - node.prev.succeeding = node.succeeding - else: - self.head = node.succeeding - if node.succeeding: - node.succeeding.prev = node.prev - else: - self.tail = node.prev - found = True - self.length -= 1 - break - node = node.succeeding - if not found: - raise ValueError("Value not found") \ No newline at end of file + self.length += 1 diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 4760945d65..e9c6a30954 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -6,19 +6,21 @@ {%- if case["expected"] and inputs|length > 1 -%} {%- set final = inputs[-1]["operation"] -%} {%- set inputs = inputs[:-1] -%} - {% if case["expected"]["error"] %} - {%- set error_case = true %} - {%- set error_msg = case["expected"]["error"] %} - {%- set error_operation = inputs[-1]["operation"] %} - {%- endif %} + {%- if case["expected"]["error"] -%} + {%- set error_case = true -%} + {%- set error_msg = case["expected"]["error"] -%} + {%- set error_operation = inputs[-1]["operation"] -%} + {% endif %} {%- elif case["expected"] and not error_case -%} {%- set final = inputs[-1]["operation"] -%} {%- set inputs = [{"operation": "None"}] -%} {%- endif -%} - {% for input in inputs -%} + {%- for input in inputs -%} {%- set operation = input["operation"] -%} {%- set value = input["value"] -%} {%- set expected = input["expected"] %} + {%- set error_msg = case["expected"]["error"] -%} + {%- set error_operation = inputs[-1]["operation"] -%} {%- if operation and value %} lst.{{ operation }}({{ value }}) {%- elif operation and expected %} @@ -27,6 +29,8 @@ {%- else %} self.assertEqual(lst.{{ operation }}(), {{ expected }}) {%- endif %} + {%- elif operation == "pop" or operation == "shift" %} + lst.{{ operation }}({{ value }}) {%- elif operation == "count" %} self.assertEqual(len(lst), {{ expected }}) {%- elif case["expected"]["error"] %} @@ -34,15 +38,15 @@ with self.assertRaises(IndexError) as err: lst.{{ case["input"]["operations"][-1]["operation"] }}() self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") - {% elif final == "delete" %} + self.assertEqual(err.exception.args[0], "{{ error_msg }}") + {%- elif final == "delete" %} with self.assertRaises(ValueError) as err: - lst.{{ case["input"]["operations"][-1]["operation"] }}() + lst.{{ case["input"]["operations"][-1]["operation"] }}({{ value if value else 0 }}) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") + self.assertEqual(err.exception.args[0], "{{ error_msg }}") {%- endif %} - {%- endif %} - {%- endfor %} + {%- endif %} + {%- endfor %} {%- if error_case %} {%- if final == "pop" or final == "shift" %} with self.assertRaises(IndexError) as err: @@ -51,7 +55,7 @@ self.assertEqual(err.exception.args[0], "{{ error_msg }}") {% elif final == "delete" %} with self.assertRaises(ValueError) as err: - lst.{{ error_operation }}() + lst.{{ final }}({{ value if value else 0 }}) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "{{ error_msg }}") {%- endif %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 0080921a2f..37d48b3a8d 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -68,13 +68,17 @@ def test_count_is_correct_after_mutation(self): self.assertEqual(len(lst), 1) lst.unshift(43) self.assertEqual(len(lst), 2) + lst.shift() self.assertEqual(len(lst), 1) + lst.pop() self.assertEqual(len(lst), 0) def test_popping_to_empty_doesn_t_break_the_list(self): lst = LinkedList() lst.push(41) lst.push(59) + lst.pop() + lst.pop() lst.push(47) self.assertEqual(len(lst), 1) self.assertEqual(lst.pop(), 47) @@ -83,6 +87,8 @@ def test_shifting_to_empty_doesn_t_break_the_list(self): lst = LinkedList() lst.push(41) lst.push(59) + lst.shift() + lst.shift() lst.push(47) self.assertEqual(len(lst), 1) self.assertEqual(lst.shift(), 47) @@ -143,12 +149,6 @@ def test_deletes_the_second_of_two_elements(self): self.assertEqual(len(lst), 1) self.assertEqual(lst.pop(), 97) - def test_delete_does_not_modify_the_list_if_the_element_is_not_found(self): - lst = LinkedList() - lst.push(89) - lst.delete(103) - self.assertEqual(len(lst), 1) - def test_deletes_only_the_first_occurrence(self): lst = LinkedList() lst.push(73) @@ -173,8 +173,8 @@ def test_can_return_with_pop_and_then_raise_an_error_if_empty(self): lst = LinkedList() lst.push(1) lst.unshift(5) - self.assertEqual(lst.pop(), 5) self.assertEqual(lst.pop(), 1) + self.assertEqual(lst.pop(), 5) with self.assertRaises(IndexError) as err: lst.pop() self.assertEqual(type(err.exception), IndexError) @@ -191,8 +191,8 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): lst = LinkedList() lst.push(1) lst.unshift(5) - self.assertEqual(lst.pop(), 5) - self.assertEqual(lst.shift(), 1) + self.assertEqual(lst.pop(), 1) + self.assertEqual(lst.shift(), 5) with self.assertRaises(IndexError) as err: lst.shift() self.assertEqual(type(err.exception), IndexError) @@ -201,7 +201,7 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): def test_using_delete_raises_an_error_if_the_list_is_empty(self): lst = LinkedList() with self.assertRaises(ValueError) as err: - lst.delete() + lst.delete(0) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "Value not found") @@ -211,6 +211,6 @@ def test_using_delete_raises_an_error_if_the_it_is_not_found(self): lst.push(7) self.assertEqual(lst.pop(), 7) with self.assertRaises(ValueError) as err: - lst.pop() + lst.delete(0) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "Value not found") From 92bf6e3f1be3735188631e446818ee3e41c6c085 Mon Sep 17 00:00:00 2001 From: Meatball Date: Thu, 8 Dec 2022 21:56:07 +0100 Subject: [PATCH 251/826] Made the error handeling shorter --- .../practice/linked-list/.meta/template.j2 | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index e9c6a30954..471ca2f098 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -3,15 +3,15 @@ def test_{{ case["description"] | to_snake }}(self): lst = LinkedList() {%- set inputs = case["input"]["operations"] -%} + {%- if case["expected"] -%} + {%- set error_case = true -%} + {%- set error_msg = case["expected"]["error"] -%} + {%- set error_operation = inputs[-1]["operation"] -%} + {% endif %} {%- if case["expected"] and inputs|length > 1 -%} {%- set final = inputs[-1]["operation"] -%} {%- set inputs = inputs[:-1] -%} - {%- if case["expected"]["error"] -%} - {%- set error_case = true -%} - {%- set error_msg = case["expected"]["error"] -%} - {%- set error_operation = inputs[-1]["operation"] -%} - {% endif %} - {%- elif case["expected"] and not error_case -%} + {%- elif case["expected"] -%} {%- set final = inputs[-1]["operation"] -%} {%- set inputs = [{"operation": "None"}] -%} {%- endif -%} @@ -33,18 +33,6 @@ lst.{{ operation }}({{ value }}) {%- elif operation == "count" %} self.assertEqual(len(lst), {{ expected }}) - {%- elif case["expected"]["error"] %} - {%- if final == "pop" or final == "shift" %} - with self.assertRaises(IndexError) as err: - lst.{{ case["input"]["operations"][-1]["operation"] }}() - self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "{{ error_msg }}") - {%- elif final == "delete" %} - with self.assertRaises(ValueError) as err: - lst.{{ case["input"]["operations"][-1]["operation"] }}({{ value if value else 0 }}) - self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "{{ error_msg }}") - {%- endif %} {%- endif %} {%- endfor %} {%- if error_case %} From 83530a738d508492e63df2dc8b67ff028bf23c75 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 8 Dec 2022 19:52:59 -0800 Subject: [PATCH 252/826] Final Edits(I hope) Added raising error message examples to instruction append. Added hint about implementing __iter__ to instruction append. Added ref links for documentation. Lightly edited example file. --- .../linked-list/.docs/instructions.apped.md | 25 ++++++++++++++++--- .../practice/linked-list/.meta/example.py | 4 --- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/exercises/practice/linked-list/.docs/instructions.apped.md b/exercises/practice/linked-list/.docs/instructions.apped.md index 7b649b857b..bf0892ff00 100644 --- a/exercises/practice/linked-list/.docs/instructions.apped.md +++ b/exercises/practice/linked-list/.docs/instructions.apped.md @@ -4,9 +4,10 @@ While linked lists can be implemented in a variety of ways with a variety of underlying data structures, we ask here that you implement your linked list in an OOP fashion. -In the stub file, you will see a `Node` class and a `LinkedList` class. +In the stub file, you will see the start of a `Node` class, as well as a `LinkedList` class. Your `Node` class should keep track of its value, as well as which other nodes preceed or follow. Your `push`, `pop`, `shift`, `unshift`, and the special method for `len` should be implemented in the `LinkedList` class. +You might also find it useful to implement a special `iter` method for iteration. In addition to the methods outlined above, we also ask that you implement `delete`. @@ -17,26 +18,42 @@ If the value appears more than once, the **first** occurance should be removed. ## Exception messages -Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. +Sometimes it is necessary to [raise an exception][raising]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][error types], but should still include a meaningful message. -This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when a node value being `delet()`-ed is not found in the linked list. Additionally, an `IndexError` should be thrown if there are no nodes left to `pop()`. The tests will only pass if you both `raise` these `exceptions` and include messages with them. +This particular exercise requires that you use the [raise statement][raise] to "throw" a `ValueError` when a node value being `delet()`-ed is not found in the linked list. Additionally, an `IndexError` should be thrown if there are no nodes left to `pop()`. The tests will only pass if you both `raise` these `exceptions` and include messages with them. To raise a `ValueError` with a message, write the message as an argument to the `exception` type: ```python # When the value passed to `delete()` is not found. +if not found: + raise ValueError("Value not found") +``` -# When pop() is called and there are no nodes left in the linked list +To raise an `IndexError` with a message, write the message as an argument to the `exception` type: +```python +# When pop() is called and there are no nodes left in the linked list +if self.length == 0: + raise IndexError("List is empty") ``` + ## Special Methods in Python The tests for this exercise will also be calling `len()` on your `LinkedList`. In order for `len()` to work, you will need to create a `__len__` special method. For details on implementing special or "dunder" methods in Python, see [Python Docs: Basic Object Customization][basic customization] and [Python Docs: object.__len__(self)][__len__]. +We also recommend creating a special [`__iter__`][__iter__] method to help with iterating over your linked list. +
+[__iter__]: https://docs.python.org/3/reference/datamodel.html#object.__iter__ +[__len__]: https://docs.python.org/3/reference/datamodel.html#object.__len__ +[basic customization]: https://docs.python.org/3/reference/datamodel.html#basic-customization +[error types]: https://docs.python.org/3/library/exceptions.html#base-classes +[raise]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[raising]: https://docs.python.org/3/tutorial/errors.html#raising-exceptions diff --git a/exercises/practice/linked-list/.meta/example.py b/exercises/practice/linked-list/.meta/example.py index 1496ef2596..acc13d4f9e 100644 --- a/exercises/practice/linked-list/.meta/example.py +++ b/exercises/practice/linked-list/.meta/example.py @@ -40,9 +40,7 @@ def delete(self, to_delete): found = True self.length -= 1 break - node = node.succeeding - if not found: raise ValueError("Value not found") @@ -67,9 +65,7 @@ def pop(self): else: self.tail = self.tail.prev self.tail.succeeding = None - self.length -= 1 - print(self.length) return node.value From 00c560771a1c9f99271d68653cb04f0146b527f5 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 8 Dec 2022 20:17:27 -0800 Subject: [PATCH 253/826] More Edits on Instruction Append --- exercises/practice/linked-list/.docs/instructions.apped.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exercises/practice/linked-list/.docs/instructions.apped.md b/exercises/practice/linked-list/.docs/instructions.apped.md index bf0892ff00..08f494b655 100644 --- a/exercises/practice/linked-list/.docs/instructions.apped.md +++ b/exercises/practice/linked-list/.docs/instructions.apped.md @@ -9,10 +9,11 @@ Your `Node` class should keep track of its value, as well as which other nodes p Your `push`, `pop`, `shift`, `unshift`, and the special method for `len` should be implemented in the `LinkedList` class. You might also find it useful to implement a special `iter` method for iteration. +Unlike the core exercise, we will be testing error conditions by calling `pop` and `shift` on empty `LinkedLists`, so you will need to `raise` errors appropriately. -In addition to the methods outlined above, we also ask that you implement `delete`. +Finally, we would like you to implement `delete` in addition to the methods outlined above. `delete` will take one argument, which is the vaule to be removed from the linked list. -If the value appears more than once, the **first** occurance should be removed. +If the value appears more than once, only the **first** occurance should be removed.
From acee9f230d79984be9bba74c989f4cc061d4c001 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 8 Dec 2022 20:31:00 -0800 Subject: [PATCH 254/826] One. Last. Change. --- exercises/practice/linked-list/.meta/template.j2 | 2 -- 1 file changed, 2 deletions(-) diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 471ca2f098..103be69f0d 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -19,8 +19,6 @@ {%- set operation = input["operation"] -%} {%- set value = input["value"] -%} {%- set expected = input["expected"] %} - {%- set error_msg = case["expected"]["error"] -%} - {%- set error_operation = inputs[-1]["operation"] -%} {%- if operation and value %} lst.{{ operation }}({{ value }}) {%- elif operation and expected %} From 09bd542547a5a9d125ae22dcc8b88516443e4277 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 9 Dec 2022 09:21:41 -0800 Subject: [PATCH 255/826] Apply suggestions from code review Co-authored-by: Katrina Owen --- exercises/practice/linked-list/.docs/instructions.apped.md | 6 +++--- exercises/practice/linked-list/.meta/additional_tests.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/linked-list/.docs/instructions.apped.md b/exercises/practice/linked-list/.docs/instructions.apped.md index 08f494b655..975494d025 100644 --- a/exercises/practice/linked-list/.docs/instructions.apped.md +++ b/exercises/practice/linked-list/.docs/instructions.apped.md @@ -12,8 +12,8 @@ You might also find it useful to implement a special `iter` method for iteration Unlike the core exercise, we will be testing error conditions by calling `pop` and `shift` on empty `LinkedLists`, so you will need to `raise` errors appropriately. Finally, we would like you to implement `delete` in addition to the methods outlined above. -`delete` will take one argument, which is the vaule to be removed from the linked list. -If the value appears more than once, only the **first** occurance should be removed. +`delete` will take one argument, which is the value to be removed from the linked list. +If the value appears more than once, only the **first** occurrence should be removed.
@@ -21,7 +21,7 @@ If the value appears more than once, only the **first** occurance should be remo Sometimes it is necessary to [raise an exception][raising]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][error types], but should still include a meaningful message. -This particular exercise requires that you use the [raise statement][raise] to "throw" a `ValueError` when a node value being `delet()`-ed is not found in the linked list. Additionally, an `IndexError` should be thrown if there are no nodes left to `pop()`. The tests will only pass if you both `raise` these `exceptions` and include messages with them. +This particular exercise requires that you use the [raise statement][raise] to "throw" a `ValueError` when a node value being `delete()`-ed is not found in the linked list. Additionally, an `IndexError` should be thrown if there are no nodes left to `pop()`. The tests will only pass if you both `raise` these `exceptions` and include messages with them. To raise a `ValueError` with a message, write the message as an argument to the `exception` type: diff --git a/exercises/practice/linked-list/.meta/additional_tests.json b/exercises/practice/linked-list/.meta/additional_tests.json index 5be9653f3f..a791426a61 100644 --- a/exercises/practice/linked-list/.meta/additional_tests.json +++ b/exercises/practice/linked-list/.meta/additional_tests.json @@ -103,7 +103,7 @@ }, { "uuid": "f7f9d2dc-be1b-4f42-be2e-c29980ce0823", - "description": "Using delete raises an error if the it is not found", + "description": "Using delete raises an error if the value is not found", "property": "list", "input": { "operations": [ From a7b7f8ec92ed16eaf282a2c4201f10717fe4702f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 9 Dec 2022 09:38:47 -0800 Subject: [PATCH 256/826] Updates/Edits Regenerated test file (don't know why it needed it, but ok. Re tested solution. Edited instruction append to be one sentence per line. --- .../practice/linked-list/.docs/instructions.apped.md | 8 ++++++-- exercises/practice/linked-list/linked_list_test.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/exercises/practice/linked-list/.docs/instructions.apped.md b/exercises/practice/linked-list/.docs/instructions.apped.md index 975494d025..00032862c2 100644 --- a/exercises/practice/linked-list/.docs/instructions.apped.md +++ b/exercises/practice/linked-list/.docs/instructions.apped.md @@ -19,9 +19,13 @@ If the value appears more than once, only the **first** occurrence should be rem ## Exception messages -Sometimes it is necessary to [raise an exception][raising]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][error types], but should still include a meaningful message. +Sometimes it is necessary to [raise an exception][raising]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. +This makes your code more readable and helps significantly with debugging. +For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][error types], but should still include a meaningful message. -This particular exercise requires that you use the [raise statement][raise] to "throw" a `ValueError` when a node value being `delete()`-ed is not found in the linked list. Additionally, an `IndexError` should be thrown if there are no nodes left to `pop()`. The tests will only pass if you both `raise` these `exceptions` and include messages with them. +This particular exercise requires that you use the [raise statement][raise] to "throw" a `ValueError` when a node value being `delete()`-ed is not found in the linked list. +Additionally, an `IndexError` should be thrown if there are no nodes left to `pop()`. +The tests will only pass if you both `raise` these `exceptions` and include messages with them. To raise a `ValueError` with a message, write the message as an argument to the `exception` type: diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 37d48b3a8d..3af5bc221e 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -205,7 +205,7 @@ def test_using_delete_raises_an_error_if_the_list_is_empty(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "Value not found") - def test_using_delete_raises_an_error_if_the_it_is_not_found(self): + def test_using_delete_raises_an_error_if_the_value_is_not_found(self): lst = LinkedList() lst.push(5) lst.push(7) From ed310e19496aa3d91114324a30b7060626e75ef6 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Fri, 9 Dec 2022 09:59:51 +0100 Subject: [PATCH 257/826] Add custom token to community contributions workflow The pause-community-contributions workflow makes a call to the GitHub API to check if the person contributing is a member of the organization. However, this call currently fails if the contributor has set their membership to 'private'. This is because the default token provided for GitHub Actions only has permissions for the repository, not for the organization. With this token, we're not allowed to see private memberships. We've created a custom, org-wide secret containing a personal token that has permissions to read organization membership. Unfortunately the secret cannot be accessed directly by the shared workflow, it has to be passed in. We updated the shared workflow to use the token, if it is provided, and this PR updates the workflow in this repo to pass the secret. Until this is merged, contributions from people with private membership in the Exercism organization will be automatically closed. Note that this PR also removes the workflow_dispatch which fails if you try to use it. --- .github/workflows/pause-community-contributions.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pause-community-contributions.yml b/.github/workflows/pause-community-contributions.yml index e91c86cf68..d764bfe8b6 100644 --- a/.github/workflows/pause-community-contributions.yml +++ b/.github/workflows/pause-community-contributions.yml @@ -10,7 +10,6 @@ on: paths-ignore: - 'exercises/*/*/.approaches/**' - 'exercises/*/*/.articles/**' - workflow_dispatch: permissions: issues: write @@ -22,3 +21,5 @@ jobs: uses: exercism/github-actions/.github/workflows/community-contributions.yml@main with: forum_category: python + secrets: + github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} From ab87e6f7e153261b478a72a98d440346e9909fb9 Mon Sep 17 00:00:00 2001 From: Meatball Date: Sat, 10 Dec 2022 23:24:12 +0100 Subject: [PATCH 258/826] Added Jinja template --- exercises/practice/react/.meta/config.json | 4 +- exercises/practice/react/.meta/template.j2 | 62 ++++ exercises/practice/react/react.py | 1 + exercises/practice/react/react_test.py | 314 +++++++++++++-------- 4 files changed, 257 insertions(+), 124 deletions(-) create mode 100644 exercises/practice/react/.meta/template.j2 diff --git a/exercises/practice/react/.meta/config.json b/exercises/practice/react/.meta/config.json index 71e3b58ad1..6ecc14244d 100644 --- a/exercises/practice/react/.meta/config.json +++ b/exercises/practice/react/.meta/config.json @@ -6,7 +6,9 @@ "Dog", "N-Parsons", "Nishant23", - "tqa236" + "tqa236", + "meatball133", + "Bethanyg" ], "files": { "solution": [ diff --git a/exercises/practice/react/.meta/template.j2 b/exercises/practice/react/.meta/template.j2 new file mode 100644 index 0000000000..f9fb637c43 --- /dev/null +++ b/exercises/practice/react/.meta/template.j2 @@ -0,0 +1,62 @@ +{%- import "generator_macros.j2" as macros with context -%} +from functools import partial +{% macro test_case(case) -%} + {%- set input = case["input"] -%} + {%- set callback = [] -%} + {%- for operation in input["operations"] -%} + {%- if operation["type"] == "add_callback" -%} + {% set callback = callback.append(operation["name"]) %} + {%- endif -%} + {%- endfor -%} + def test_{{ case["description"] | to_snake }}(self): + {%- for cell in input["cells"] -%} + {%- if cell["type"] == "input" %} + {{ cell["name"] }} = InputCell({{ cell["initial_value"] }}) + {%- elif cell["type"] == "compute" -%} + {%- if "if" in cell["compute_function"] %} + output = ComputeCell([input], lambda inputs: 111 if inputs[0] < 3 else 222) + {%- else %} + {{ cell["name"] }} = ComputeCell([{%- for input in cell["inputs"] -%}{{ input }},{%- endfor -%}], lambda inputs: {{ cell["compute_function"] }}) + {%- endif -%} + {%- endif -%} + {%- endfor -%} + {%- if callback -%} + {%- for _ in callback %} + cb{{ loop.index0 +1 }}_observer = [] + {%- endfor -%} + {%- endif %} + {% for sub_callback in callback -%} + {{ sub_callback }} = self.callback_factory(cb{{ loop.index0 + 1 }}_observer) + {% endfor -%} + {%- for operation in input["operations"] -%} + {%- if operation["type"] == "add_callback" or operation["type"] == "remove_callback" -%} + {{ operation["cell"] }}.{{ operation["type"] }}({{ operation["name"] }}) + {%- elif operation["type"] == "expect_cell_value" -%} + self.assertEqual({{ operation["cell"] }}.value, {{ operation["value"] }}) + {%- elif operation["type"] == "set_value" -%} + {{ operation["cell"] }}.value = {{ operation["value"] }} + {%- if operation["expect_callbacks_not_to_be_called"] %} + {%- if callback | length == 3 %} + self.assertEqual(len(cb{{ operation["expect_callbacks_not_to_be_called"][0][-1] }}_observer), 1) + {%- else %} + self.assertEqual(cb{{ operation["expect_callbacks_not_to_be_called"][0][-1] }}_observer, []) + {%- endif %} + {%- endif -%} + {%- for exp_callback in operation["expect_callbacks"] %} + self.assertEqual(cb{{ exp_callback[-1] }}_observer[-1], {{ operation["expect_callbacks"][exp_callback] }}) + {%- endfor -%} + {%- endif %} + {% endfor -%} +{%- endmacro %} +{{ macros.header(["InputCell", "ComputeCell"])}} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + {{ test_case(case) }} + {% endfor %} + + # Utility functions. + def callback_factory(self, observer): + def callback(observer, value): + observer.append(value) + return partial(callback, observer) diff --git a/exercises/practice/react/react.py b/exercises/practice/react/react.py index 03ff02e789..ab6be311d9 100644 --- a/exercises/practice/react/react.py +++ b/exercises/practice/react/react.py @@ -12,3 +12,4 @@ def add_callback(self, callback): def remove_callback(self, callback): pass + \ No newline at end of file diff --git a/exercises/practice/react/react_test.py b/exercises/practice/react/react_test.py index 7c1870cfda..2ba86a80ff 100644 --- a/exercises/practice/react/react_test.py +++ b/exercises/practice/react/react_test.py @@ -1,202 +1,270 @@ -import unittest from functools import partial -from react import InputCell, ComputeCell +import unittest + +from react import ( + InputCell, + ComputeCell, +) +# Tests adapted from `problem-specifications//canonical-data.json` -# Tests adapted from `problem-specifications//canonical-data.json` @ v2.0.0 class ReactTest(unittest.TestCase): - def test_input_cells_have_a_value(self): - input_ = InputCell(10) - self.assertEqual(input_.value, 10) + input = InputCell(10) + self.assertEqual(input.value, 10) - def test_can_set_input_cell_value(self): - input_ = InputCell(4) - input_.value = 20 - self.assertEqual(input_.value, 20) + def test_an_input_cell_s_value_can_be_set(self): + input = InputCell(4) + input.value = 20 + self.assertEqual(input.value, 20) def test_compute_cells_calculate_initial_value(self): - input_ = InputCell(1) - output = ComputeCell([input_], lambda inputs: inputs[0] + 1) + input = InputCell(1) + output = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] + 1, + ) self.assertEqual(output.value, 2) - def test_compute_cells_take_inputs_in_right_order(self): + def test_compute_cells_take_inputs_in_the_right_order(self): one = InputCell(1) two = InputCell(2) output = ComputeCell( - [one, two], - lambda inputs: inputs[0] + inputs[1]*10 + [ + one, + two, + ], + lambda inputs: inputs[0] + inputs[1] * 10, ) self.assertEqual(output.value, 21) def test_compute_cells_update_value_when_dependencies_are_changed(self): - input_ = InputCell(1) - output = ComputeCell([input_], lambda inputs: inputs[0] + 1) - - input_.value = 3 + input = InputCell(1) + output = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] + 1, + ) + input.value = 3 self.assertEqual(output.value, 4) def test_compute_cells_can_depend_on_other_compute_cells(self): - input_ = InputCell(1) - times_two = ComputeCell([input_], lambda inputs: inputs[0] * 2) - times_thirty = ComputeCell([input_], lambda inputs: inputs[0] * 30) + input = InputCell(1) + times_two = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] * 2, + ) + times_thirty = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] * 30, + ) output = ComputeCell( - [times_two, times_thirty], - lambda inputs: inputs[0] + inputs[1] + [ + times_two, + times_thirty, + ], + lambda inputs: inputs[0] + inputs[1], ) - self.assertEqual(output.value, 32) - input_.value = 3 + input.value = 3 self.assertEqual(output.value, 96) def test_compute_cells_fire_callbacks(self): - input_ = InputCell(1) - output = ComputeCell([input_], lambda inputs: inputs[0] + 1) - - observer = [] - callback1 = self.callback_factory(observer) - - output.add_callback(callback1) - input_.value = 3 - self.assertEqual(observer[-1], 4) - - def test_callbacks_only_fire_on_change(self): - input_ = InputCell(1) + input = InputCell(1) output = ComputeCell( - [input_], - lambda inputs: 111 if inputs[0] < 3 else 222 + [ + input, + ], + lambda inputs: inputs[0] + 1, ) + cb1_observer = [] + callback1 = self.callback_factory(cb1_observer) + output.add_callback(callback1) + input.value = 3 + self.assertEqual(cb1_observer[-1], 4) - observer = [] - callback1 = self.callback_factory(observer) - + def test_callback_cells_only_fire_on_change(self): + input = InputCell(1) + output = ComputeCell([input], lambda inputs: 111 if inputs[0] < 3 else 222) + cb1_observer = [] + callback1 = self.callback_factory(cb1_observer) output.add_callback(callback1) - input_.value = 2 - self.assertEqual(observer, []) - input_.value = 4 - self.assertEqual(observer[-1], 222) + input.value = 2 + self.assertEqual(cb1_observer, []) + input.value = 4 + self.assertEqual(cb1_observer[-1], 222) def test_callbacks_do_not_report_already_reported_values(self): - input_ = InputCell(1) - output = ComputeCell([input_], lambda inputs: inputs[0] + 1) - - observer = [] - callback1 = self.callback_factory(observer) - + input = InputCell(1) + output = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] + 1, + ) + cb1_observer = [] + callback1 = self.callback_factory(cb1_observer) output.add_callback(callback1) - input_.value = 2 - self.assertEqual(observer[-1], 3) - input_.value = 3 - self.assertEqual(observer[-1], 4) + input.value = 2 + self.assertEqual(cb1_observer[-1], 3) + input.value = 3 + self.assertEqual(cb1_observer[-1], 4) def test_callbacks_can_fire_from_multiple_cells(self): - input_ = InputCell(1) - plus_one = ComputeCell([input_], lambda inputs: inputs[0] + 1) - minus_one = ComputeCell([input_], lambda inputs: inputs[0] - 1) - - cb1_observer, cb2_observer = [], [] + input = InputCell(1) + plus_one = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] + 1, + ) + minus_one = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] - 1, + ) + cb1_observer = [] + cb2_observer = [] callback1 = self.callback_factory(cb1_observer) callback2 = self.callback_factory(cb2_observer) - plus_one.add_callback(callback1) minus_one.add_callback(callback2) - input_.value = 10 - + input.value = 10 self.assertEqual(cb1_observer[-1], 11) self.assertEqual(cb2_observer[-1], 9) def test_callbacks_can_be_added_and_removed(self): - input_ = InputCell(11) - output = ComputeCell([input_], lambda inputs: inputs[0] + 1) - - cb1_observer, cb2_observer, cb3_observer = [], [], [] + input = InputCell(11) + output = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] + 1, + ) + cb1_observer = [] + cb2_observer = [] + cb3_observer = [] callback1 = self.callback_factory(cb1_observer) callback2 = self.callback_factory(cb2_observer) callback3 = self.callback_factory(cb3_observer) - output.add_callback(callback1) output.add_callback(callback2) - input_.value = 31 + input.value = 31 self.assertEqual(cb1_observer[-1], 32) self.assertEqual(cb2_observer[-1], 32) - output.remove_callback(callback1) output.add_callback(callback3) - input_.value = 41 + input.value = 41 + self.assertEqual(len(cb1_observer), 1) self.assertEqual(cb2_observer[-1], 42) self.assertEqual(cb3_observer[-1], 42) - # Expect callback1 not to be called. - self.assertEqual(len(cb1_observer), 1) - - def test_removing_a_callback_multiple_times(self): - """Guard against incorrect implementations which store their - callbacks in an array.""" - input_ = InputCell(1) - output = ComputeCell([input_], lambda inputs: inputs[0] + 1) - - cb1_observer, cb2_observer = [], [] + def test_removing_a_callback_multiple_times_doesn_t_interfere_with_other_callbacks( + self, + ): + input = InputCell(1) + output = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] + 1, + ) + cb1_observer = [] + cb2_observer = [] callback1 = self.callback_factory(cb1_observer) callback2 = self.callback_factory(cb2_observer) - output.add_callback(callback1) output.add_callback(callback2) output.remove_callback(callback1) output.remove_callback(callback1) output.remove_callback(callback1) - input_.value = 2 - + input.value = 2 self.assertEqual(cb1_observer, []) self.assertEqual(cb2_observer[-1], 3) - def test_callbacks_should_only_be_called_once(self): - """Guard against incorrect implementations which call a callback - function multiple times when multiple dependencies change.""" - input_ = InputCell(1) - plus_one = ComputeCell([input_], lambda inputs: inputs[0] + 1) - minus_one1 = ComputeCell([input_], lambda inputs: inputs[0] - 1) - minus_one2 = ComputeCell([minus_one1], lambda inputs: inputs[0] - 1) + def test_callbacks_should_only_be_called_once_even_if_multiple_dependencies_change( + self, + ): + input = InputCell(1) + plus_one = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] + 1, + ) + minus_one1 = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] - 1, + ) + minus_one2 = ComputeCell( + [ + minus_one1, + ], + lambda inputs: inputs[0] - 1, + ) output = ComputeCell( - [plus_one, minus_one2], - lambda inputs: inputs[0] * inputs[1] + [ + plus_one, + minus_one2, + ], + lambda inputs: inputs[0] * inputs[1], ) - - observer = [] - callback1 = self.callback_factory(observer) - + cb1_observer = [] + callback1 = self.callback_factory(cb1_observer) output.add_callback(callback1) - input_.value = 4 - self.assertEqual(observer[-1], 10) - - def test_callbacks_not_called_so_long_as_output_not_changed(self): - """Guard against incorrect implementations which call callbacks - if dependencies change but output value doesn't change.""" - input_ = InputCell(1) - plus_one = ComputeCell([input_], lambda inputs: inputs[0] + 1) - minus_one = ComputeCell([input_], lambda inputs: inputs[0] - 1) + input.value = 4 + self.assertEqual(cb1_observer[-1], 10) + + def test_callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesn_t_change( + self, + ): + input = InputCell(1) + plus_one = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] + 1, + ) + minus_one = ComputeCell( + [ + input, + ], + lambda inputs: inputs[0] - 1, + ) always_two = ComputeCell( - [plus_one, minus_one], - lambda inputs: inputs[0] - inputs[1] + [ + plus_one, + minus_one, + ], + lambda inputs: inputs[0] - inputs[1], ) - - observer = [] - callback1 = self.callback_factory(observer) - + cb1_observer = [] + callback1 = self.callback_factory(cb1_observer) always_two.add_callback(callback1) - input_.value = 2 - input_.value = 3 - input_.value = 4 - input_.value = 5 - self.assertEqual(observer, []) + input.value = 2 + self.assertEqual(cb1_observer, []) + input.value = 3 + self.assertEqual(cb1_observer, []) + input.value = 4 + self.assertEqual(cb1_observer, []) + input.value = 5 + self.assertEqual(cb1_observer, []) # Utility functions. def callback_factory(self, observer): def callback(observer, value): observer.append(value) - return partial(callback, observer) - -if __name__ == '__main__': - unittest.main() + return partial(callback, observer) From c5da4971d562bf401502292be7bcd981d8abfc31 Mon Sep 17 00:00:00 2001 From: PaulT89 <97411029+PaulT89@users.noreply.github.com> Date: Sun, 11 Dec 2022 09:58:59 +1100 Subject: [PATCH 259/826] [Pascals Triangle] Remastered for recursion (#3150) * [Pascals Triangle] Remastered for recursion Rewrote example.py: - Uses recursion rather than loops. - Raises a meaningful error when row_count < 0, instead of just returning None. Added template.j2: - Based general layout of the template on the old pascals_triangle_test file. Added instructions.append.md: - Encourage student to use recursion to solve this problem - Added boilerplate message about how to raise exceptions .meta/config.json: - Added name as co-author (though maybe contributor would be more appropriate?) config.json: - practices recursion - added (seemingly) sensible sounding prerequisites - kept difficulty - status = wip for now - moved entire block up to be in line with other exercises of similar difficulty. * [Pascals Triangle] Remastered for recursion Rewrote example.py: - Uses recursion rather than loops. - Raises a meaningful error when row_count < 0, instead of just returning None. Added template.j2: - Based general layout of the template on the old pascals_triangle_test file. Added instructions.append.md: - Encourage student to use recursion to solve this problem - Added boilerplate message about how to raise exceptions .meta/config.json: - Added name as co-author (though maybe contributor would be more appropriate?) config.json: - practices recursion - added (seemingly) sensible sounding prerequisites - kept difficulty - moved entire block up to be in line with other exercises of similar difficulty. * Updated Exercise -Fixed config prerequisite bug (exercise is now accessible). - Moved hardcoded additional tests to additional_tests.json file. - Added test for recursion. * Fixed bug Moved import sys * Fixed template indentation * Manual recursion limit * Revert "Manual recursion limit" This reverts commit f11762983ff1a548cb8930fa83c07d637b722a05. * Minor formatting changes * Canonical sync fix Apparently the RecursionError message changed in Python 3.9. Hopefully this will work across all tested versions now. * [Pascals Triangle] Remastered for recursion Rewrote example.py: - Uses recursion rather than loops. - Raises a meaningful error when row_count < 0, instead of just returning None. Added template.j2: - Based general layout of the template on the old pascals_triangle_test file. Added instructions.append.md: - Encourage student to use recursion to solve this problem - Added boilerplate message about how to raise exceptions .meta/config.json: - Added name as co-author (though maybe contributor would be more appropriate?) config.json: - practices recursion - added (seemingly) sensible sounding prerequisites - kept difficulty - moved entire block up to be in line with other exercises of similar difficulty. * [Pascals Triangle] Remastered for recursion Rewrote example.py: - Uses recursion rather than loops. - Raises a meaningful error when row_count < 0, instead of just returning None. Added template.j2: - Based general layout of the template on the old pascals_triangle_test file. Added instructions.append.md: - Encourage student to use recursion to solve this problem - Added boilerplate message about how to raise exceptions .meta/config.json: - Added name as co-author (though maybe contributor would be more appropriate?) config.json: - practices recursion - added (seemingly) sensible sounding prerequisites - kept difficulty - status = wip for now - moved entire block up to be in line with other exercises of similar difficulty. * Updated Exercise -Fixed config prerequisite bug (exercise is now accessible). - Moved hardcoded additional tests to additional_tests.json file. - Added test for recursion. * Fixed bug Moved import sys * Fixed template indentation * Manual recursion limit * Revert "Manual recursion limit" This reverts commit f11762983ff1a548cb8930fa83c07d637b722a05. * Minor formatting changes * Canonical sync fix Apparently the RecursionError message changed in Python 3.9. Hopefully this will work across all tested versions now. * Updated instructions -Updated Instructions.append.md -Added hints.md * Template changes and small reflink edits Shortened the JinJa Template a bit. Edited instruction append to use reflinks and a recursion widget, and be one sentence per line. Retested everything. * Update config.json * Update config.json Co-authored-by: BethanyG --- config.json | 22 ++++++---- .../practice/pascals-triangle/.docs/hints.md | 10 +++++ .../.docs/instructions.append.md | 43 +++++++++++++++++++ .../.meta/additional_tests.json | 26 +++++++++++ .../pascals-triangle/.meta/config.json | 3 +- .../pascals-triangle/.meta/example.py | 14 +++--- .../pascals-triangle/.meta/template.j2 | 39 +++++++++++++++++ .../pascals-triangle/pascals_triangle_test.py | 32 +++++++++----- 8 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 exercises/practice/pascals-triangle/.docs/hints.md create mode 100644 exercises/practice/pascals-triangle/.docs/instructions.append.md create mode 100644 exercises/practice/pascals-triangle/.meta/additional_tests.json create mode 100644 exercises/practice/pascals-triangle/.meta/template.j2 diff --git a/config.json b/config.json index f267cbd83a..6c8340b289 100644 --- a/config.json +++ b/config.json @@ -1271,6 +1271,19 @@ ], "difficulty": 3 }, + { + "slug": "pascals-triangle", + "name": "Pascals Triangle", + "uuid": "e1e1c7d7-c1d9-4027-b90d-fad573182419", + "practices": ["recursion"], + "prerequisites": [ + "basics", + "conditionals", + "lists", + "numbers" + ], + "difficulty": 4 + }, { "slug": "grep", "name": "Grep", @@ -2140,15 +2153,6 @@ "difficulty": 3, "status": "deprecated" }, - { - "slug": "pascals-triangle", - "name": "Pascal's Triangle", - "uuid": "e1e1c7d7-c1d9-4027-b90d-fad573182419", - "practices": [], - "prerequisites": [], - "difficulty": 3, - "status": "deprecated" - }, { "slug": "point-mutations", "name": "Point Mutations", diff --git a/exercises/practice/pascals-triangle/.docs/hints.md b/exercises/practice/pascals-triangle/.docs/hints.md new file mode 100644 index 0000000000..235e786ff8 --- /dev/null +++ b/exercises/practice/pascals-triangle/.docs/hints.md @@ -0,0 +1,10 @@ +# Hints + +## General + +- A more detailed description of recursive programming can be found [here][g4g] +- This exercise involves a test to ensure that you used a recursive solution +- If you are having trouble completing this exercise, try [using a loop first, and then convert it into a recursive solution][educative] + +[g4g]: https://www.geeksforgeeks.org/recursion/ +[educative]: https://www.educative.io/collection/page/6151088528949248/4547996664463360/6292303276670976 diff --git a/exercises/practice/pascals-triangle/.docs/instructions.append.md b/exercises/practice/pascals-triangle/.docs/instructions.append.md new file mode 100644 index 0000000000..3b236ae489 --- /dev/null +++ b/exercises/practice/pascals-triangle/.docs/instructions.append.md @@ -0,0 +1,43 @@ +# Instructions append + +## How this Exercise is Implemented in Python: Recursion + +This exercise is designed to be completed using [concept:python/recursion](), rather than loops. +A recursive function is a function that calls itself, which is useful when solving problems that are defined in terms of themselves. +To avoid infinite recursion (or more specifically, to avoid overflowing the stack), something called a "base case" is used. +When the base case is reached, a non-recursive value is returned, which allows the previous function call to resolve and return its value, and so on, rippling back down the stack until the first function call returns the answer. +We could write a recursive function to find the answer to 5! (i.e. 5 * 4 * 3 * 2 * 1) like so: + +````python +def factorial(number): + if number <= 1: # base case + return 1 + + return number * factorial(number - 1) # recursive case + +print(factorial(5)) # returns 120 +```` + +Finally, it should be noted that Python limits the number of times recursive calls can be made (1000 by default) and does not optimize for [tail recursion][tail-recursion]. + +## Exception messages + +Sometimes it is necessary to [raise an exception][raising]. +When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. +This makes your code more readable and helps significantly with debugging. +For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][built-in-errors], but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement][raise-statement] to "throw" multiple `ValueErrors` if the `rows()` function is passed a negative number. +The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# if the rows function is passed a negative number. +raise ValueError("number of rows is negative") +``` + +[built-in-errors]: https://docs.python.org/3/library/exceptions.html#base-classes +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[raising]: https://docs.python.org/3/tutorial/errors.html#raising-exceptions +[tail-recursion]: https://www.geeksforgeeks.org/tail-recursion/ diff --git a/exercises/practice/pascals-triangle/.meta/additional_tests.json b/exercises/practice/pascals-triangle/.meta/additional_tests.json new file mode 100644 index 0000000000..7c1375ab5a --- /dev/null +++ b/exercises/practice/pascals-triangle/.meta/additional_tests.json @@ -0,0 +1,26 @@ +{ + "cases": [ + { + "description": "negative rows are invalid", + "property": "rows", + "input": { + "count": -1 + }, + "expected": { + "error": "number of rows is negative", + "type": "ValueError" + } + }, + { + "description": "solution is recursive", + "property": "rows", + "input": { + "count": "OVERFLOW" + }, + "expected": { + "error": "maximum recursion depth exceeded", + "type": "RecursionError" + } + } + ] +} diff --git a/exercises/practice/pascals-triangle/.meta/config.json b/exercises/practice/pascals-triangle/.meta/config.json index 167b937cce..4793e16f1c 100644 --- a/exercises/practice/pascals-triangle/.meta/config.json +++ b/exercises/practice/pascals-triangle/.meta/config.json @@ -1,7 +1,8 @@ { "blurb": "Compute Pascal's triangle up to a given number of rows.", "authors": [ - "betegelse" + "betegelse", + "PaulT89" ], "contributors": [ "behrtam", diff --git a/exercises/practice/pascals-triangle/.meta/example.py b/exercises/practice/pascals-triangle/.meta/example.py index 70f3aec1dd..9181199bf2 100644 --- a/exercises/practice/pascals-triangle/.meta/example.py +++ b/exercises/practice/pascals-triangle/.meta/example.py @@ -1,12 +1,8 @@ -def rows(row_count): +def rows(row_count, previous_row=[1]): if row_count < 0: - return None + raise ValueError("number of rows is negative") elif row_count == 0: return [] - row_list = [] - for idx in range(row_count): - ronald = [1] - for edx in range(idx): - ronald.append(sum(row_list[-1][edx:edx+2])) - row_list.append(ronald) - return row_list + temp_row = previous_row + [0] + new_row = list(map(sum, zip(temp_row, temp_row[::-1]))) + return [previous_row] + rows(row_count - 1, new_row) diff --git a/exercises/practice/pascals-triangle/.meta/template.j2 b/exercises/practice/pascals-triangle/.meta/template.j2 new file mode 100644 index 0000000000..0041fd53c2 --- /dev/null +++ b/exercises/practice/pascals-triangle/.meta/template.j2 @@ -0,0 +1,39 @@ +{%- import "generator_macros.j2" as macros with context -%} +import sys +{{ macros.header() }} + +TRIANGLE = [ +[1], +[1, 1], +[1, 2, 1], +[1, 3, 3, 1], +[1, 4, 6, 4, 1], +[1, 5, 10, 10, 5, 1], +[1, 6, 15, 20, 15, 6, 1], +[1, 7, 21, 35, 35, 21, 7, 1], +[1, 8, 28, 56, 70, 56, 28, 8, 1], +[1, 9, 36, 84, 126, 126, 84, 36, 9, 1] +] + +class {{ exercise | camel_case }}Test(unittest.TestCase): +{%- for case in cases %} + def test_{{ case["description"] | to_snake }}(self): + self.assertEqual({{ case["property"] }}({{ case["input"]["count"] }}), TRIANGLE[:{{ case["input"]["count"] }}]) +{% endfor %} + +# Additional tests for this track +{%- for case in additional_cases %} + def test_{{ case["description"] | to_snake }}(self): + {%- if case is error_case %} + with self.assertRaises({{ case["expected"]["type"] }}) as err: + {%- if case["input"]["count"] == "OVERFLOW" %} + rows(sys.getrecursionlimit() + 10) + self.assertEqual(type(err.exception), RecursionError) + self.assertEqual(err.exception.args[0][:32], "maximum recursion depth exceeded") + {%- else %} + rows(-1) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "number of rows is negative") + {%- endif %} + {%- endif %} +{% endfor %} diff --git a/exercises/practice/pascals-triangle/pascals_triangle_test.py b/exercises/practice/pascals-triangle/pascals_triangle_test.py index c2c5068145..dc7b7ce111 100644 --- a/exercises/practice/pascals-triangle/pascals_triangle_test.py +++ b/exercises/practice/pascals-triangle/pascals_triangle_test.py @@ -1,9 +1,11 @@ +import sys import unittest -from pascals_triangle import rows +from pascals_triangle import ( + rows, +) - -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0 +# Tests adapted from `problem-specifications//canonical-data.json` TRIANGLE = [ [1], @@ -15,13 +17,13 @@ [1, 6, 15, 20, 15, 6, 1], [1, 7, 21, 35, 35, 21, 7, 1], [1, 8, 28, 56, 70, 56, 28, 8, 1], - [1, 9, 36, 84, 126, 126, 84, 36, 9, 1] + [1, 9, 36, 84, 126, 126, 84, 36, 9, 1], ] class PascalsTriangleTest(unittest.TestCase): def test_zero_rows(self): - self.assertEqual(rows(0), []) + self.assertEqual(rows(0), TRIANGLE[:0]) def test_single_row(self): self.assertEqual(rows(1), TRIANGLE[:1]) @@ -44,9 +46,17 @@ def test_six_rows(self): def test_ten_rows(self): self.assertEqual(rows(10), TRIANGLE[:10]) - def test_negative_rows(self): - self.assertEqual(rows(-1), None) - - -if __name__ == '__main__': - unittest.main() + # Additional tests for this track + def test_negative_rows_are_invalid(self): + with self.assertRaises(ValueError) as err: + rows(-1) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "number of rows is negative") + + def test_solution_is_recursive(self): + with self.assertRaises(RecursionError) as err: + rows(sys.getrecursionlimit() + 10) + self.assertEqual(type(err.exception), RecursionError) + self.assertEqual( + err.exception.args[0][:32], "maximum recursion depth exceeded" + ) From ee4d9e4120a68b094c32be03ad6baa3882eae045 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 11 Dec 2022 01:44:49 +0100 Subject: [PATCH 260/826] [Exercise]: added square-root (#3279) * added square-root * Instruction Append Co-authored-by: BethanyG --- config.json | 13 +++++++++ .../square-root/.docs/instructions.append.md | 16 +++++++++++ .../square-root/.docs/instructions.md | 13 +++++++++ .../practice/square-root/.meta/config.json | 21 ++++++++++++++ .../practice/square-root/.meta/example.py | 5 ++++ .../practice/square-root/.meta/template.j2 | 15 ++++++++++ .../practice/square-root/.meta/tests.toml | 28 +++++++++++++++++++ exercises/practice/square-root/square_root.py | 2 ++ .../practice/square-root/square_root_test.py | 27 ++++++++++++++++++ 9 files changed, 140 insertions(+) create mode 100644 exercises/practice/square-root/.docs/instructions.append.md create mode 100644 exercises/practice/square-root/.docs/instructions.md create mode 100644 exercises/practice/square-root/.meta/config.json create mode 100644 exercises/practice/square-root/.meta/example.py create mode 100644 exercises/practice/square-root/.meta/template.j2 create mode 100644 exercises/practice/square-root/.meta/tests.toml create mode 100644 exercises/practice/square-root/square_root.py create mode 100644 exercises/practice/square-root/square_root_test.py diff --git a/config.json b/config.json index 6c8340b289..d1399f2f3f 100644 --- a/config.json +++ b/config.json @@ -593,6 +593,19 @@ ], "difficulty": 2 }, + { + "slug": "square-root", + "name": "Square Root", + "uuid": "c32f994a-1080-4f05-bf88-051975a75d64", + "practices": ["number"], + "prerequisites": [ + "basics", + "number", + "conditionals", + "loops" + ], + "difficulty": 2 + }, { "slug": "scrabble-score", "name": "Scrabble Score", diff --git a/exercises/practice/square-root/.docs/instructions.append.md b/exercises/practice/square-root/.docs/instructions.append.md new file mode 100644 index 0000000000..535038e466 --- /dev/null +++ b/exercises/practice/square-root/.docs/instructions.append.md @@ -0,0 +1,16 @@ +# Instructions append + +## How this Exercise is Structured in Python + + +Python offers a wealth of mathematical functions in the form of the [math module][math-module] and built-ins such as [`pow()`][pow] and [`sum()`][sum]. +However, we'd like you to consider the challenge of solving this exercise without those built-ins or modules. + +While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of only testing [natural numbers][nautral-number] (positive integers). + + +[math-module]: https://docs.python.org/3/library/math.html +[pow]: https://docs.python.org/3/library/functions.html#pow +[sum]: https://docs.python.org/3/library/functions.html#sum +[nautral-number]: https://en.wikipedia.org/wiki/Natural_number + diff --git a/exercises/practice/square-root/.docs/instructions.md b/exercises/practice/square-root/.docs/instructions.md new file mode 100644 index 0000000000..e9905e9d41 --- /dev/null +++ b/exercises/practice/square-root/.docs/instructions.md @@ -0,0 +1,13 @@ +# Instructions + +Given a natural radicand, return its square root. + +Note that the term "radicand" refers to the number for which the root is to be determined. +That is, it is the number under the root symbol. + +Check out the Wikipedia pages on [square root][square-root] and [methods of computing square roots][computing-square-roots]. + +Recall also that natural numbers are positive real whole numbers (i.e. 1, 2, 3 and up). + +[square-root]: https://en.wikipedia.org/wiki/Square_root +[computing-square-roots]: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots diff --git a/exercises/practice/square-root/.meta/config.json b/exercises/practice/square-root/.meta/config.json new file mode 100644 index 0000000000..b1b9630b31 --- /dev/null +++ b/exercises/practice/square-root/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "meatball133", + "Bethanyg" + ], + "contributors": [], + "files": { + "solution": [ + "square_root.py" + ], + "test": [ + "square_root_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Given a natural radicand, return its square root.", + "source": "wolf99", + "source_url": "https://github.com/exercism/problem-specifications/pull/1582" +} diff --git a/exercises/practice/square-root/.meta/example.py b/exercises/practice/square-root/.meta/example.py new file mode 100644 index 0000000000..94cd0fdc71 --- /dev/null +++ b/exercises/practice/square-root/.meta/example.py @@ -0,0 +1,5 @@ +def square_root(number): + n = 0 + while n ** 2 != number: + n += 1 + return n diff --git a/exercises/practice/square-root/.meta/template.j2 b/exercises/practice/square-root/.meta/template.j2 new file mode 100644 index 0000000000..9334317859 --- /dev/null +++ b/exercises/practice/square-root/.meta/template.j2 @@ -0,0 +1,15 @@ +{%- import "generator_macros.j2" as macros with context -%} +{% macro test_case(case) -%} + {%- set input = case["input"] -%} + def test_{{ case["description"] | to_snake }}(self): + self.assertEqual( + {{ case["property"] | to_snake }}({{ case["input"]["radicand"] }}), + {{ case["expected"] }} + ) +{%- endmacro %} +{{ macros.header()}} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + {{ test_case(case) }} + {% endfor %} diff --git a/exercises/practice/square-root/.meta/tests.toml b/exercises/practice/square-root/.meta/tests.toml new file mode 100644 index 0000000000..ead7882fc3 --- /dev/null +++ b/exercises/practice/square-root/.meta/tests.toml @@ -0,0 +1,28 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[9b748478-7b0a-490c-b87a-609dacf631fd] +description = "root of 1" + +[7d3aa9ba-9ac6-4e93-a18b-2e8b477139bb] +description = "root of 4" + +[6624aabf-3659-4ae0-a1c8-25ae7f33c6ef] +description = "root of 25" + +[93beac69-265e-4429-abb1-94506b431f81] +description = "root of 81" + +[fbddfeda-8c4f-4bc4-87ca-6991af35360e] +description = "root of 196" + +[c03d0532-8368-4734-a8e0-f96a9eb7fc1d] +description = "root of 65025" diff --git a/exercises/practice/square-root/square_root.py b/exercises/practice/square-root/square_root.py new file mode 100644 index 0000000000..0a2fc38927 --- /dev/null +++ b/exercises/practice/square-root/square_root.py @@ -0,0 +1,2 @@ +def square_root(number): + pass diff --git a/exercises/practice/square-root/square_root_test.py b/exercises/practice/square-root/square_root_test.py new file mode 100644 index 0000000000..635728fee4 --- /dev/null +++ b/exercises/practice/square-root/square_root_test.py @@ -0,0 +1,27 @@ +import unittest + +from square_root import ( + square_root, +) + +# Tests adapted from `problem-specifications//canonical-data.json` + + +class SquareRootTest(unittest.TestCase): + def test_root_of_1(self): + self.assertEqual(square_root(1), 1) + + def test_root_of_4(self): + self.assertEqual(square_root(4), 2) + + def test_root_of_25(self): + self.assertEqual(square_root(25), 5) + + def test_root_of_81(self): + self.assertEqual(square_root(81), 9) + + def test_root_of_196(self): + self.assertEqual(square_root(196), 14) + + def test_root_of_65025(self): + self.assertEqual(square_root(65025), 255) From 0765a702e117ba7aad6d80578f3cfb4f5e17e86b Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:21:02 -0600 Subject: [PATCH 261/826] Update content.md (#3280) Changed `This concept is also aparat of` to `This concept is also a part of`. --- .../practice/wordy/.approaches/dunder-getattribute/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index f64bc6fac3..dd3e6de39d 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -76,7 +76,7 @@ It sets the list to the result of the dunder method plus the remaining elements ```exercism/note The `*` prefix in `*tail` [unpacks](https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/) the `tail` list back into its elements. -This concept is also aparat of [unpacking-and-multiple-assignment](https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment) concept in the syllabus.. +This concept is also a part of [unpacking-and-multiple-assignment](https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment) concept in the syllabus. ``` When the loop exhausts, the first element of the list is selected as the function return value. From 090a5eb2828c4acd36f0cf6e9fa855ec2ed3f016 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Dec 2022 23:01:30 +0000 Subject: [PATCH 262/826] Bump exercism/pr-commenter-action from 1.3.1 to 1.4.0 Bumps [exercism/pr-commenter-action](https://github.com/exercism/pr-commenter-action) from 1.3.1 to 1.4.0. - [Release notes](https://github.com/exercism/pr-commenter-action/releases) - [Changelog](https://github.com/exercism/pr-commenter-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/exercism/pr-commenter-action/compare/v1.3.1...v1.4.0) --- updated-dependencies: - dependency-name: exercism/pr-commenter-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/pr-commenter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index 12591d4900..13c0b62d44 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -6,7 +6,7 @@ jobs: pr-comment: runs-on: ubuntu-latest steps: - - uses: exercism/pr-commenter-action@v1.3.1 + - uses: exercism/pr-commenter-action@v1.4.0 with: github-token: "${{ github.token }}" config-file: ".github/pr-commenter.yml" \ No newline at end of file From 6ac97d58b28f8669f9e332bfcb8399cb5efd192d Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Sun, 18 Dec 2022 10:53:15 -0800 Subject: [PATCH 263/826] Update SGF Parsing template, tests and escape function --- .../practice/sgf-parsing/.meta/example.py | 215 +++++++++--------- .../practice/sgf-parsing/.meta/template.j2 | 8 +- .../practice/sgf-parsing/.meta/tests.toml | 8 +- .../practice/sgf-parsing/sgf_parsing_test.py | 46 +++- 4 files changed, 160 insertions(+), 117 deletions(-) diff --git a/exercises/practice/sgf-parsing/.meta/example.py b/exercises/practice/sgf-parsing/.meta/example.py index 7711625518..369c7de643 100644 --- a/exercises/practice/sgf-parsing/.meta/example.py +++ b/exercises/practice/sgf-parsing/.meta/example.py @@ -1,108 +1,115 @@ -class SgfTree: - def __init__(self, properties=None, children=None): - self.properties = properties or {} - self.children = children or [] - - def __eq__(self, other): - if not isinstance(other, SgfTree): - return False - - for key, value in self.properties.items(): - if key not in other.properties: - return False - - if other.properties[key] != value: - return False - - for key in other.properties.keys(): - if key not in self.properties: - return False - - if len(self.children) != len(other.children): - return False - - for child, other_child in zip(self.children, other.children): - if not child == other_child: - return False - return True - - def __repr__(self): - """Ironically, encoding to SGF is much easier.""" - rep = '(;' - for key, property_value in self.properties.items(): - rep += key - for value in property_value: - rep += '[{}]'.format(value) - if self.children: - if len(self.children) > 1: - rep += '(' - for child in self.children: - rep += repr(child)[1:-1] - if len(self.children) > 1: - rep += ')' - return rep + ')' - - -def parse(input_string): - root = None - current = None - stack = list(input_string) - if input_string == '()': - raise ValueError('tree with no nodes') +"""Parse an SGF tree.""" +from __future__ import annotations - if not stack: - raise ValueError('tree missing') +import collections +import dataclasses +import string - def pop(): - if stack[0] == '\\': - stack.pop(0) - characters = stack.pop(0) - return ' ' if characters in ['\t'] else characters - - def pop_until(delimiter): - try: - value = '' - while stack[0] != delimiter: - if stack[0] == "\n": - stack[0] = "n" - if stack[0] == "\t": - stack[0] = "t" - value += pop() - return value - except IndexError as error: - raise ValueError('properties without delimiter') from error - - while stack: - if pop() == '(' and stack[0] == ';': - while pop() == ';': - properties = {} - while stack[0].isupper(): - key = pop_until('[') - if not key.isupper(): - raise ValueError('property must be in uppercase') - values = [] - while stack[0] == '[': - pop() - values.append(pop_until(']')) - pop() - properties[key] = values - - if stack[0].isalpha(): - if not stack[0].isupper(): - raise ValueError('property must be in uppercase') - - if root is None: - current = root = SgfTree(properties) +@dataclasses.dataclass +class SgfTree: + """SGF Node.""" + + properties: dict[str, str] = dataclasses.field(default_factory=dict) + children: list[SgfTree] = dataclasses.field(default_factory=list) + + +def parse_property_vals(sgf: str, idx: int) -> tuple[int, list[str]]: + """Parse property values, returning the next index and values.""" + values = [] + while idx < len(sgf): + if sgf[idx] != "[": + break + + # Start of the value. + idx += 1 + prop_val = "" + while sgf[idx] != "]": + # \ has special SGF handling. + if sgf[idx] == "\\": + if sgf[idx:idx + 2] == "\\\n": + # Newlines are removed if they come immediately after a \, + # otherwise they remain as newlines. + pass else: - current = SgfTree(properties) - root.children.append(current) - - while stack[0] == '(': - child_input = pop() + pop_until(')') + pop() - current.children.append(parse(child_input)) - - elif root is None and current is None: - raise ValueError('tree missing') - - return root + # \ is the escape character. Any non-whitespace character + # after \ is inserted as-is + prop_val += sgf[idx + 1] + idx += 2 + else: + prop_val += sgf[idx] + idx += 1 + + # All whitespace characters other than newline are converted to spaces. + for char in string.whitespace: + if char == "\n": + continue + prop_val = prop_val.replace(char, " ") + + values.append(prop_val) + idx += 1 + + return idx, values + + +def parse_node(sgf: str) -> SgfTree: + """Parse and return a Node.""" + if not sgf.startswith(";"): + raise ValueError("node must start with ';'") + + idx = 1 + prop_key_start = idx + + properties = collections.defaultdict(list) + children = [] + + while idx < len(sgf): + match sgf[idx]: + case "[": + # Parse property values. + if idx == prop_key_start: + raise ValueError("propery key is empty") + prop_key = sgf[prop_key_start:idx] + if not prop_key.isupper(): + raise ValueError('property must be in uppercase') + + idx, prop_vals = parse_property_vals(sgf, idx) + properties[prop_key].extend(prop_vals) + + # New property. + prop_key_start = idx + case ";": + # Single child. + child = parse_node(sgf[idx:]) + children.append(child) + break + case "(": + # Multiple children. + children = [] + while idx < len(sgf): + if sgf[idx] != "(": + break + # Child start. + idx += 1 + child_start = idx + while sgf[idx] != ")": + idx += 1 + # Child end. + child = parse_node(sgf[child_start:idx]) + children.append(child) + idx += 1 + case _: + idx += 1 + + if idx > prop_key_start and not properties: + raise ValueError('properties without delimiter') + return SgfTree(children=children, properties=dict(properties)) + + +def parse(sgf: str) -> SgfTree: + """Parse an SGF tree.""" + if not sgf.startswith("(") and not sgf.endswith(")"): + raise ValueError('tree missing') + if not sgf.startswith("(;"): + raise ValueError('tree with no nodes') + return parse_node(sgf.removeprefix("(").removesuffix(")")) diff --git a/exercises/practice/sgf-parsing/.meta/template.j2 b/exercises/practice/sgf-parsing/.meta/template.j2 index f3da29b39c..7017a43648 100644 --- a/exercises/practice/sgf-parsing/.meta/template.j2 +++ b/exercises/practice/sgf-parsing/.meta/template.j2 @@ -1,8 +1,12 @@ {%- import "generator_macros.j2" as macros with context -%} +{% macro escape_sequences(string) -%} + {{ string | replace("\\", "\\\\") | replace("\n", "\\n") | replace("\t", "\\t") }} +{%- endmacro -%} + {% macro test_case(case) -%} def test_{{ case["description"] | to_snake }}(self): - input_string = '{{ case["input"]["encoded"] | escape_invalid_escapes }}' + input_string = '{{ escape_sequences(case["input"]["encoded"]) }}' {% if case["expected"]["error"] -%} with self.assertRaises(ValueError) as err: {{ case["property"] | to_snake }}(input_string) @@ -33,7 +37,7 @@ {%- for key, values in properties.items() -%} '{{ key }}':[ {%- for value in values -%} - '{{ value }}'{% if not loop.last %}, {% endif -%} + '{{ escape_sequences(value) }}'{% if not loop.last %}, {% endif -%} {% endfor -%}] {%- if not loop.last %}, {% endif -%} {% endfor -%} } diff --git a/exercises/practice/sgf-parsing/.meta/tests.toml b/exercises/practice/sgf-parsing/.meta/tests.toml index 71f5b83a64..2a9d7d927b 100644 --- a/exercises/practice/sgf-parsing/.meta/tests.toml +++ b/exercises/practice/sgf-parsing/.meta/tests.toml @@ -47,23 +47,18 @@ description = "multiple property values" [28092c06-275f-4b9f-a6be-95663e69d4db] description = "within property values, whitespace characters such as tab are converted to spaces" -include = false [deaecb9d-b6df-4658-aa92-dcd70f4d472a] -description = "within property values, newli es remain as newlines" -include = false +description = "within property values, newlines remain as newlines" [8e4c970e-42d7-440e-bfef-5d7a296868ef] description = "escaped closing bracket within property value becomes just a closing bracket" -include = false [cf371fa8-ba4a-45ec-82fb-38668edcb15f] description = "escaped backslash in property value becomes just a backslash" -include = false [dc13ca67-fac0-4b65-b3fe-c584d6a2c523] description = "opening bracket within property value doesn't need to be escaped" -include = false [a780b97e-8dbb-474e-8f7e-4031902190e8] description = "semicolon in property value doesn't need to be escaped" @@ -83,7 +78,6 @@ description = "escaped t and n in property value are just letters, not whitespac [08e4b8ba-bb07-4431-a3d9-b1f4cdea6dab] description = "mixing various kinds of whitespace and escaped characters in property value" reimplements = "11c36323-93fc-495d-bb23-c88ee5844b8c" -include = false [11c36323-93fc-495d-bb23-c88ee5844b8c] description = "escaped property" diff --git a/exercises/practice/sgf-parsing/sgf_parsing_test.py b/exercises/practice/sgf-parsing/sgf_parsing_test.py index 9b01fb11d0..b3d43ed09d 100644 --- a/exercises/practice/sgf-parsing/sgf_parsing_test.py +++ b/exercises/practice/sgf-parsing/sgf_parsing_test.py @@ -84,6 +84,38 @@ def test_multiple_property_values(self): expected = SgfTree(properties={"A": ["b", "c", "d"]}) self.assertEqual(parse(input_string), expected) + def test_within_property_values_whitespace_characters_such_as_tab_are_converted_to_spaces( + self, + ): + input_string = "(;A[hello\t\tworld])" + expected = SgfTree(properties={"A": ["hello world"]}) + self.assertEqual(parse(input_string), expected) + + def test_within_property_values_newlines_remain_as_newlines(self): + input_string = "(;A[hello\n\nworld])" + expected = SgfTree(properties={"A": ["hello\n\nworld"]}) + self.assertEqual(parse(input_string), expected) + + def test_escaped_closing_bracket_within_property_value_becomes_just_a_closing_bracket( + self, + ): + input_string = "(;A[\\]])" + expected = SgfTree(properties={"A": ["]"]}) + self.assertEqual(parse(input_string), expected) + + def test_escaped_backslash_in_property_value_becomes_just_a_backslash(self): + input_string = "(;A[\\\\])" + expected = SgfTree(properties={"A": ["\\"]}) + self.assertEqual(parse(input_string), expected) + + def test_opening_bracket_within_property_value_doesn_t_need_to_be_escaped(self): + input_string = "(;A[x[y\\]z][foo]B[bar];C[baz])" + expected = SgfTree( + properties={"A": ["x[y]z", "foo"], "B": ["bar"]}, + children=[SgfTree({"C": ["baz"]})], + ) + self.assertEqual(parse(input_string), expected) + def test_semicolon_in_property_value_doesn_t_need_to_be_escaped(self): input_string = "(;A[a;b][foo]B[bar];C[baz])" expected = SgfTree( @@ -101,17 +133,23 @@ def test_parentheses_in_property_value_don_t_need_to_be_escaped(self): self.assertEqual(parse(input_string), expected) def test_escaped_tab_in_property_value_is_converted_to_space(self): - input_string = "(;A[hello\\ world])" + input_string = "(;A[hello\\\tworld])" expected = SgfTree(properties={"A": ["hello world"]}) self.assertEqual(parse(input_string), expected) def test_escaped_newline_in_property_value_is_converted_to_nothing_at_all(self): - input_string = "(;A[hello\ -world])" + input_string = "(;A[hello\\\nworld])" expected = SgfTree(properties={"A": ["helloworld"]}) self.assertEqual(parse(input_string), expected) def test_escaped_t_and_n_in_property_value_are_just_letters_not_whitespace(self): - input_string = "(;A[\t = t and \n = n])" + input_string = "(;A[\\t = t and \\n = n])" expected = SgfTree(properties={"A": ["t = t and n = n"]}) self.assertEqual(parse(input_string), expected) + + def test_mixing_various_kinds_of_whitespace_and_escaped_characters_in_property_value( + self, + ): + input_string = "(;A[\\]b\nc\\\nd\t\te\\\\ \\\n\\]])" + expected = SgfTree(properties={"A": ["]b\ncd e\\ ]"]}) + self.assertEqual(parse(input_string), expected) From cfbebf6708d1ab3d81519b7beed3f097159dfa4a Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Sun, 18 Dec 2022 12:32:57 -0800 Subject: [PATCH 264/826] Bump flake8 version for requirements-generator from 3.7.8 to 5.0.4 --- requirements-generator.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-generator.txt b/requirements-generator.txt index 44391b2d66..3c096bebc1 100644 --- a/requirements-generator.txt +++ b/requirements-generator.txt @@ -1,6 +1,6 @@ black==22.3.0 -flake8==3.7.8 +flake8~=5.0.4 Jinja2~=3.1.2 python-dateutil==2.8.1 markupsafe==2.0.1 -tomli~=2.0.1 \ No newline at end of file +tomli~=2.0.1 From 33afa506a44a0991bb121b02ab618eacf29f2bcb Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Sun, 18 Dec 2022 12:35:38 -0800 Subject: [PATCH 265/826] Add IsaacG as a contributor to SGF Parsing --- exercises/practice/sgf-parsing/.meta/config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/sgf-parsing/.meta/config.json b/exercises/practice/sgf-parsing/.meta/config.json index 284ea0fb2f..565a8ce0d5 100644 --- a/exercises/practice/sgf-parsing/.meta/config.json +++ b/exercises/practice/sgf-parsing/.meta/config.json @@ -7,6 +7,7 @@ "crsmi", "Dog", "elyashiv", + "IsaacG", "thomasjpfan", "tqa236", "yawpitch" From 2fba89110c2567b3e7f0ff7c6e00cdc910fd2bbd Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Sun, 18 Dec 2022 12:43:51 -0800 Subject: [PATCH 266/826] Drop newer features like `match case` and str.removeprefix --- .../practice/sgf-parsing/.meta/example.py | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/exercises/practice/sgf-parsing/.meta/example.py b/exercises/practice/sgf-parsing/.meta/example.py index 369c7de643..2b14e37153 100644 --- a/exercises/practice/sgf-parsing/.meta/example.py +++ b/exercises/practice/sgf-parsing/.meta/example.py @@ -64,42 +64,41 @@ def parse_node(sgf: str) -> SgfTree: children = [] while idx < len(sgf): - match sgf[idx]: - case "[": - # Parse property values. - if idx == prop_key_start: - raise ValueError("propery key is empty") - prop_key = sgf[prop_key_start:idx] - if not prop_key.isupper(): - raise ValueError('property must be in uppercase') - - idx, prop_vals = parse_property_vals(sgf, idx) - properties[prop_key].extend(prop_vals) - - # New property. - prop_key_start = idx - case ";": - # Single child. - child = parse_node(sgf[idx:]) - children.append(child) - break - case "(": - # Multiple children. - children = [] - while idx < len(sgf): - if sgf[idx] != "(": - break - # Child start. - idx += 1 - child_start = idx - while sgf[idx] != ")": - idx += 1 - # Child end. - child = parse_node(sgf[child_start:idx]) - children.append(child) + if sgf[idx] == "[": + # Parse property values. + if idx == prop_key_start: + raise ValueError("propery key is empty") + prop_key = sgf[prop_key_start:idx] + if not prop_key.isupper(): + raise ValueError('property must be in uppercase') + + idx, prop_vals = parse_property_vals(sgf, idx) + properties[prop_key].extend(prop_vals) + + # New property. + prop_key_start = idx + elif sgf[idx] == ";": + # Single child. + child = parse_node(sgf[idx:]) + children.append(child) + break + elif sgf[idx] == "(": + # Multiple children. + children = [] + while idx < len(sgf): + if sgf[idx] != "(": + break + # Child start. + idx += 1 + child_start = idx + while sgf[idx] != ")": idx += 1 - case _: + # Child end. + child = parse_node(sgf[child_start:idx]) + children.append(child) idx += 1 + else: + idx += 1 if idx > prop_key_start and not properties: raise ValueError('properties without delimiter') @@ -112,4 +111,5 @@ def parse(sgf: str) -> SgfTree: raise ValueError('tree missing') if not sgf.startswith("(;"): raise ValueError('tree with no nodes') - return parse_node(sgf.removeprefix("(").removesuffix(")")) + inside = sgf[1:-1] + return parse_node(inside) From 2e58919bfb22b20f89782b79a6a06e0bc3c4c90d Mon Sep 17 00:00:00 2001 From: Meatball Date: Mon, 19 Dec 2022 09:34:52 +0100 Subject: [PATCH 267/826] removed 3 lines --- exercises/practice/linked-list/.meta/template.j2 | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 103be69f0d..a753efc3df 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -9,11 +9,9 @@ {%- set error_operation = inputs[-1]["operation"] -%} {% endif %} {%- if case["expected"] and inputs|length > 1 -%} - {%- set final = inputs[-1]["operation"] -%} {%- set inputs = inputs[:-1] -%} {%- elif case["expected"] -%} - {%- set final = inputs[-1]["operation"] -%} - {%- set inputs = [{"operation": "None"}] -%} + {%- set inputs = [] -%} {%- endif -%} {%- for input in inputs -%} {%- set operation = input["operation"] -%} @@ -34,17 +32,16 @@ {%- endif %} {%- endfor %} {%- if error_case %} - {%- if final == "pop" or final == "shift" %} + {%- if error_operation == "pop" or error_operation == "shift" %} with self.assertRaises(IndexError) as err: lst.{{ error_operation }}() self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "{{ error_msg }}") - {% elif final == "delete" %} + {%- elif error_operation == "delete" %} with self.assertRaises(ValueError) as err: - lst.{{ final }}({{ value if value else 0 }}) + lst.{{ error_operation }}({{ value if value else 0 }}) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "{{ error_msg }}") {%- endif %} + self.assertEqual(err.exception.args[0], "{{ error_msg }}") {%- endif %} {%- endmacro %} From 2107057937c77f0a3e36f7154483d5cb7c7f8498 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 22 Dec 2022 22:15:27 +0100 Subject: [PATCH 268/826] Fix file-name --- .../.docs/{instructions.apped.md => instructions.append.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exercises/practice/linked-list/.docs/{instructions.apped.md => instructions.append.md} (100%) diff --git a/exercises/practice/linked-list/.docs/instructions.apped.md b/exercises/practice/linked-list/.docs/instructions.append.md similarity index 100% rename from exercises/practice/linked-list/.docs/instructions.apped.md rename to exercises/practice/linked-list/.docs/instructions.append.md From ad8e8c94058ada935479b22024811b5e2f0e0c8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 23:01:32 +0000 Subject: [PATCH 269/826] Bump actions/stale from 6 to 7 Bumps [actions/stale](https://github.com/actions/stale) from 6 to 7. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9e4ecd1d5c..4742a0db0a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v6 + - uses: actions/stale@v7 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 From a45dce1c28c66812800eb9fa5b0bacb37f6bd892 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Wed, 21 Dec 2022 20:47:56 +0100 Subject: [PATCH 270/826] First push --- .../electric-bill/.docs/instructions.md | 81 +++++++++++++++++++ .../concept/electric-bill/.meta/config.json | 19 +++++ .../concept/electric-bill/.meta/exemplar.py | 48 +++++++++++ .../concept/electric-bill/electric_bill.py | 48 +++++++++++ .../electric-bill/electric_bill_test.py | 58 +++++++++++++ 5 files changed, 254 insertions(+) create mode 100644 exercises/concept/electric-bill/.docs/instructions.md create mode 100644 exercises/concept/electric-bill/.meta/config.json create mode 100644 exercises/concept/electric-bill/.meta/exemplar.py create mode 100644 exercises/concept/electric-bill/electric_bill.py create mode 100644 exercises/concept/electric-bill/electric_bill_test.py diff --git a/exercises/concept/electric-bill/.docs/instructions.md b/exercises/concept/electric-bill/.docs/instructions.md new file mode 100644 index 0000000000..c8022915da --- /dev/null +++ b/exercises/concept/electric-bill/.docs/instructions.md @@ -0,0 +1,81 @@ +# Instructions + +The company you work for want to reduce their carbon footprint, so they wants you to write a program which calculates the power usage of their electronics and the cost of running them. + +1. Get extra hours + +Your employer wants a program that calculates the time it takes to run different electronics. +Currently the time is stored in hours. +When your employer added the hours they noticed that the time was not correct and they want you to add 3 extra hours to the time data. +They also would like to know how many hours needs to be removed to get the data in full days(24 hours). +The time given doesn't have to be in full days. + +Implement a function `get_extra_hours()` that accepts an integer which holds the number of hours. +The function should then `return` an integer with how many hours which has to be removed to get the time in full days. + +```python +>>> get_extra_hours(25) +4 +``` + +2. Get kW value + +Your employer wants to know the power usage of the different electronics. +They want to know the power usage in kW. +kW stands for kilowatt, there watts is a unit of power. +Kilo in the unit name is a prefix in the metric system meaning 1000. +So 1 kilowatt is equal to 1000 watts. + +Implement a function `get_kW_value()` that accepts an integer which holds the number of watts. +The function should then `return` the watts as kilowatts rounded to 1 decimal. + +```python +>>> get_kW_value(1150) +1.2 +``` + +3. Get kwh value + +To be able to calculate the cost of running the electronics your employer wants to know the power usage in kWh. +kWh stands for kilowatt-hour, there hour is a unit of time. +So 1 kilowatt-hour is equal to 1000 watts used for 1 hour. +An hour is made of 60 minutes and a minute is made of 60 seconds. +So 1 hour is equal to 3600 seconds. +To get the kWh value you have to have to convert the watts to kW and then floor-divide it by 3600. + +Implement a function `get_kWh_value()` that accepts an integer which holds the number of watts. +The function should then `return` the watts as an integer. + +```python +>>> get_kWh_value(5000000) +1 +``` + +4. Get efficiency + +Electronics are not 100% efficient. +Therefore your employer wants to know the efficiency of the electronics. +To get efficiency you have to divide the power factor by 100. +The power factor is a float between 0 and 100. + +Implement a function `get_efficiency()` that accepts a float that holds the power factor. +The function should then `return` the power factor as a float. + +```python +>>> get_efficiency(80) +0.8 +``` + +5. Get cost + +Your employer wants to know the cost of running the electronics. +The power used is the power given divided by the efficiency. +The cost of running the electronics is the power used multiplied by the cost per kWh. + +Implement a function `get_cost()` that accepts an integer that holds the number of watts and a float that has the power factor and a float that holds the cost per kwh. +The function should then `return` the cost of running the electronics as a float rounded to 2 decimals. + +```python +>>> get_cost(5000000, 80, 0.25) +0.3125 +``` diff --git a/exercises/concept/electric-bill/.meta/config.json b/exercises/concept/electric-bill/.meta/config.json new file mode 100644 index 0000000000..d1560b2033 --- /dev/null +++ b/exercises/concept/electric-bill/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "meatball133", + "BethanyG" + ], + "files": { + "solution": [ + "electric_bill.py" + ], + "test": [ + "electric_bill_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "city-office", + "blurb": "to do" +} diff --git a/exercises/concept/electric-bill/.meta/exemplar.py b/exercises/concept/electric-bill/.meta/exemplar.py new file mode 100644 index 0000000000..996b9e3afa --- /dev/null +++ b/exercises/concept/electric-bill/.meta/exemplar.py @@ -0,0 +1,48 @@ +"""Functions which helps company to calculate their power usage""" + + +def get_the_amount_of_hours(hours, missing_hours): + """Return the amount of hours. + + :param: hours: int - amount of hours. + :return: int - amount of hours. + """ + return (hours + missing_hours) % 24 + + +def get_kW_value(watts): + """Return the kW value of a given watt value. + + :param: watts: int - watt value. + :return: float - kW value. + """ + return round(watts / 1000, 1) # rounds here + + +def get_kwh_value(watts): + """Return the kWh value of a given watt value and hours. + + :param: watts: int - watt value. + :param: hours: int - kilowatt hour value. + """ + return get_kW_value(watts) // 3600 + + +def get_efficiency(efficiency): + """Return the efficiency as a power factor. + + :param: efficiency: float - efficiency. + :return: float - efficiency. + """ + return efficiency / 100 + + +def get_price_of_kwh(watts, efficiency, price): + """Return the price of a given kWh value, efficiency and price. + + :param: watts: int - watt value. + :param: efficiency: float - efficiency. + :param: price: float - price of kWh. + :return: float - price of kWh. + """ + return price * (get_kwh_value(watts) / get_efficiency(efficiency)) diff --git a/exercises/concept/electric-bill/electric_bill.py b/exercises/concept/electric-bill/electric_bill.py new file mode 100644 index 0000000000..e7e90c59c6 --- /dev/null +++ b/exercises/concept/electric-bill/electric_bill.py @@ -0,0 +1,48 @@ +"""Functions which helps company to calculate their power usage""" + + +def get_the_amount_of_hours(hours): + """Return the amount of hours. + + :param: hours: int - amount of hours. + :return: int - amount of hours. + """ + return (hours + 3) % 24 + + +def get_kW_value(watts): + """Return the kW value of a given watt value. + + :param: watts: int - watt value. + :return: float - kW value. + """ + return round(watts / 1000, 1) # rounds here + + +def get_kwh_value(watts): + """Return the kWh value of a given watt value and hours. + + :param: watts: int - watt value. + :param: hours: int - kilowatt hour value. + """ + return int(get_kW_value(watts) // 3600) + + +def get_efficiency(efficiency): + """Return the efficiency as a power factor. + + :param: efficiency: float - efficiency. + :return: float - efficiency. + """ + return efficiency / 100 + + +def get_price_of_kwh(watts, efficiency, price): + """Return the price of a given kWh value, efficiency and price. + + :param: watts: int - watt value. + :param: efficiency: float - efficiency. + :param: price: float - price of kWh. + :return: float - price of kWh. + """ + return price * (get_kwh_value(watts) / get_efficiency(efficiency)) diff --git a/exercises/concept/electric-bill/electric_bill_test.py b/exercises/concept/electric-bill/electric_bill_test.py new file mode 100644 index 0000000000..95745e74a8 --- /dev/null +++ b/exercises/concept/electric-bill/electric_bill_test.py @@ -0,0 +1,58 @@ +import unittest +import pytest +from electric_bill import (get_the_amount_of_hours, + get_kW_value, + get_kwh_value, + get_efficiency, + get_price_of_kwh) + + +class LocomotiveEngineerTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_get_the_amount_of_hours(self): + input_data = [25, 10, 5, 2, 1, 120, 21] + output_data = [4, 13, 8, 5, 4, 3, 0] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different value.' + self.assertEqual(get_the_amount_of_hours(input_data), output_data, msg=error_msg) + + @pytest.mark.task(taskno=2) + def test_get_kW_value(self): + input_data = [1000, 2200, 2900, 900, 1160] + output_data = [1, 2.2, 2.9, 0.9, 1.2] + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different value.' + self.assertEqual(get_kW_value(input_data), output_data, msg=error_msg) + + @pytest.mark.task(taskno=3) + def test_get_kwh_value(self): + input_data = (5000000, 2141241, 43252135, 5324623462, 4321512) + output_data = [1, 0, 12, 1479, 1] + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different value.' + self.assertEqual(get_kwh_value(input_data), output_data, msg=error_msg) + + @pytest.mark.task(taskno=4) + def test_get_efficiency(self): + input_data = [80.0, 99.99, 0.8, 40.0] + output_data = [0.8, 0.9999, 0.008, 0.4] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different value.' + self.assertAlmostEqual(get_efficiency(input_data), output_data, msg=error_msg) + + @pytest.mark.task(taskno=5) + def test_get_price_of_kwh(self): + input_data = ((5000000, 80.0, 0.25), (2141241, 99.99, 2), (43252135, 0.8, 4), (4321512, 40.0, 2)) + output_data = (0.3125, 0, 6000, 5) + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different value.' + self.assertEqual(get_price_of_kwh(*input_data), output_data, msg=error_msg) From a4d877299b6a57c6f0cf12ef1f364b5460276c8f Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 21 Dec 2022 21:21:52 +0100 Subject: [PATCH 271/826] Update --- .../concept/electric-bill/.docs/instructions.md | 13 ++++++------- exercises/concept/electric-bill/.meta/exemplar.py | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/exercises/concept/electric-bill/.docs/instructions.md b/exercises/concept/electric-bill/.docs/instructions.md index c8022915da..ee81c2b23f 100644 --- a/exercises/concept/electric-bill/.docs/instructions.md +++ b/exercises/concept/electric-bill/.docs/instructions.md @@ -54,12 +54,11 @@ The function should then `return` the watts as an integer. 4. Get efficiency Electronics are not 100% efficient. -Therefore your employer wants to know the efficiency of the electronics. -To get efficiency you have to divide the power factor by 100. -The power factor is a float between 0 and 100. +Therefore, your employer wants you to calculate the efficiency of the electronics. +To get efficiency you have to divide the power factor (a float between 0 and 100) by 100. Implement a function `get_efficiency()` that accepts a float that holds the power factor. -The function should then `return` the power factor as a float. +The function should then `return` the calculated efficiency as a float. ```python >>> get_efficiency(80) @@ -69,11 +68,11 @@ The function should then `return` the power factor as a float. 5. Get cost Your employer wants to know the cost of running the electronics. -The power used is the power given divided by the efficiency. The cost of running the electronics is the power used multiplied by the cost per kWh. +The power used is the power given divided by the calculated efficiency. -Implement a function `get_cost()` that accepts an integer that holds the number of watts and a float that has the power factor and a float that holds the cost per kwh. -The function should then `return` the cost of running the electronics as a float rounded to 2 decimals. +Implement a function `get_cost()` that accepts an integer that holds the number of watts, a float that has the power factor, and a float that holds the cost per kwh. +The function should then `return` the cost of running the electronics as a float. ```python >>> get_cost(5000000, 80, 0.25) diff --git a/exercises/concept/electric-bill/.meta/exemplar.py b/exercises/concept/electric-bill/.meta/exemplar.py index 996b9e3afa..7102c5d6c1 100644 --- a/exercises/concept/electric-bill/.meta/exemplar.py +++ b/exercises/concept/electric-bill/.meta/exemplar.py @@ -1,13 +1,13 @@ """Functions which helps company to calculate their power usage""" -def get_the_amount_of_hours(hours, missing_hours): +def get_the_amount_of_hours(hours): """Return the amount of hours. :param: hours: int - amount of hours. :return: int - amount of hours. """ - return (hours + missing_hours) % 24 + return (hours + 3) % 24 def get_kW_value(watts): From a652bb712c1874381c45af9e4f8eff32c456c20b Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 22 Dec 2022 22:13:20 +0100 Subject: [PATCH 272/826] started --- concepts/numbers/about.md | 136 +++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index e746972d1f..34ddd9573c 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -15,115 +15,115 @@ Whole numbers (_including hex, octals and binary numbers_) **without** decimal p -12 ``` -Hex numbers are prefixed with `0x`: +Numbers containing a decimal point (with or without fractional parts) are identified as `floats`: ```python -# Hex numbers start with 0x. ->>> 0x17 -23 ->>> type(0x17) - +>>> 3.45 +3.45 +>>> type(3.45) + ``` -Octals are prefixed with a `0o`: +## Arithmetic + +Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). + +### Addition and subtraction + +Addition and subtraction act like in normal math. +If one of the operands is a `float`, the other will be converted to a `float` as well. +Otherwise both operands will be converted to `ints`: ```python -# Octal numbers start with a 0o. ->>> 0o446 -294 ->>> type(0o446) - +>>> 5 - 3 +2 +# The int is widened to a float here, and a float is returned. +>>> 3 + 4.0 +7.0 ``` -Binary numbers are prefixed with `0b`, and written with only zeros and ones: +### Multiplication + +As with addition and subtraction, multiplication will convert narrower numbers to match their less narrow counterparts: ```python -# Binary numbers are made up of 0s and 1s. ->>> 0b1100110 -102 ->>> type(0b1100110) - +>>> 3 * 2 +6 + +>>> 3 * 2.0 +6.0 ``` -Each of these `int` displays can be converted into the other via constructor: +### Division -```python +Division always returns a `float`, even if the result is a whole number: ->>> starting_number = 1234 +```python +>>> 6/5 +1.2 ->>> hex(starting_number) -'0x4d2' +>>> 6/2 +3.0 +``` ->>> oct(starting_number) -'0o2322' +### Floor division ->>> bin(starting_number) -'0b10011010010' +If an `int` result is needed, you can use floor division to truncate the result. +Floor division is performed using the `//` operator: ->>> hex(0b10011010010) -'0x4d2' +```python +>>> 6//5 +1 ->>> int(0x4d2) -1234 +>>> 6//2 +3 ``` -Numbers containing a decimal point (with or without fractional parts) are identified as `floats`: +### Modulo + +The modulo operator (`%`) returns the remainder of the division of the two operands: ```python ->>> 3.45 -3.45 ->>> type(3.45) - +>>> 5 % 3 +2 ``` -Appending `j` or `J` to a number creates an _imaginary number_ -- a `complex` number with a zero real part. `ints` or `floats` can then be added to an imaginary number to create a `complex` number with both real and imaginary parts: +### Exponentiation -```python ->>> 3j -3j ->>> type(3j) - +Exponentiation is performed using the `**` operator: ->>> 3.5+4j -(3.5+4j) +```python +>>> 2 ** 3 +8 ``` -## Arithmetic +All numbers (except complex) support all [arithmetic operations][arethmetic-operations], evaluated according to [operator precedence][operator precedence]. Support for mathematical functions (beyond `+`, `-`, `/`) for complex numbers can be found in the [cmath][cmath] module. -Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). +## Conversions -Python considers `ints` narrower than `floats`, which are considered narrower than `complex` numbers. Comparisons between different number types behave as if the _exact_ values of those numbers were being compared: +Numbers can be converted from one type to another using the built-in functions `int()` and `float()`: ```python -# The int is widened to a float here, and a float is returned. ->>> 3 + 4.0 -7.0 - -# The int is widened to a complex number, and a complex number is returned. ->>> 6/(3+2j) -(2+2j) +>>> int(3.45) +3 -# Division always returns a float, even if integers are used. ->>> 6/2 +>>> float(3) 3.0 +``` -# If an int result is needed, you can use floor division to truncate the result. ->>> 6//2 -3 +## Round -# When comparing, exact values are used. ->>> 23 == 0x17 -True +Python provides a built-in function `round()` to round off a floating point number to a given number of decimal places. +If no number of decimal places is specified, the number is rounded off to the nearest integer and will return an `int`: ->>> 0b10111 == 0x17 -True +```python +>>> round(3.1415926535, 2) +3.14 ->>> 6 == (6+0j) -True +>>> round(3.1415926535) +3 ``` -All numbers (except complex) support all [arithmetic operations][arethmetic-operations], evaluated according to [operator precedence][operator precedence]. Support for mathematical functions (beyond `+`, `-`, `/`) for complex numbers can be found in the [cmath][cmath] module. - ## Precision & Representation Integers in Python have [arbitrary precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) -- the amount of digits is limited only by the available memory of the host system. From edce70c43929ed46160f5d339ccf7a4d0262c328 Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 23 Dec 2022 16:59:18 +0100 Subject: [PATCH 273/826] Add round --- concepts/numbers/about.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 34ddd9573c..1ea487843f 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -113,7 +113,7 @@ Numbers can be converted from one type to another using the built-in functions ` ## Round -Python provides a built-in function `round()` to round off a floating point number to a given number of decimal places. +Python provides a built-in function `round(number, )` to round off a floating point number to a given number of decimal places. If no number of decimal places is specified, the number is rounded off to the nearest integer and will return an `int`: ```python @@ -124,13 +124,42 @@ If no number of decimal places is specified, the number is rounded off to the ne 3 ``` +## Priority and parentheses + +Python allows you to use parentheses to group expressions. +This is useful when you want to override the default order of operations. + +```python +>>> 2 + 3 * 4 +14 + +>>> (2 + 3) * 4 +20 +``` + +Python follows the [PEMDAS][operator precedence] rule for operator precedence. +This means `()` have the highest priority, followed by `**`, then `*`, `/`, `//`, `%`, `+`, and `-`: + +```python +>>> 2 + 3 - 4 * 4 +-11 + +>>> (2 + 3 - 4) * 4 +20 + +# In the following example, the `**` operator has the highest priority, then `*`, then `+` +# Meaning we first do 4 ** 4, then 3 * 64, then 2 + 192 +>>> 2 + 3 * 4 ** 4 +770 +``` + ## Precision & Representation Integers in Python have [arbitrary precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) -- the amount of digits is limited only by the available memory of the host system. Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. -For a more detailed discussions of the issues and limitations of floating point arithmetic across programming langages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. +For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. [int]: https://docs.python.org/3/library/functions.html#int [float]: https://docs.python.org/3/library/functions.html#float From eed14b390110b245c0c24de7e10b049794110ea5 Mon Sep 17 00:00:00 2001 From: Carl Date: Sat, 24 Dec 2022 23:16:56 +0100 Subject: [PATCH 274/826] Minor changes --- concepts/numbers/about.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 1ea487843f..2c3863aa58 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -31,7 +31,7 @@ Python fully supports arithmetic between these different number types, and will ### Addition and subtraction Addition and subtraction act like in normal math. -If one of the operands is a `float`, the other will be converted to a `float` as well. +If atleast one of the operands is a `float`, the other will be converted to a `float` as well. Otherwise both operands will be converted to `ints`: ```python @@ -86,6 +86,9 @@ The modulo operator (`%`) returns the remainder of the division of the two opera ```python >>> 5 % 3 2 + +>>> 8 % 2 +0 ``` ### Exponentiation @@ -95,13 +98,16 @@ Exponentiation is performed using the `**` operator: ```python >>> 2 ** 3 8 + +>>> 4 ** 0.5 +2 ``` All numbers (except complex) support all [arithmetic operations][arethmetic-operations], evaluated according to [operator precedence][operator precedence]. Support for mathematical functions (beyond `+`, `-`, `/`) for complex numbers can be found in the [cmath][cmath] module. ## Conversions -Numbers can be converted from one type to another using the built-in functions `int()` and `float()`: +Numbers can be converted from `int` to `floats` and `floats` to `int` using the built-in functions `int()` and `float()`: ```python >>> int(3.45) @@ -138,7 +144,7 @@ This is useful when you want to override the default order of operations. ``` Python follows the [PEMDAS][operator precedence] rule for operator precedence. -This means `()` have the highest priority, followed by `**`, then `*`, `/`, `//`, `%`, `+`, and `-`: +This means calculations within `()` have the highest priority, followed by `**`, then `*`, `/`, `//`, `%`, `+`, and `-`: ```python >>> 2 + 3 - 4 * 4 From 37ed22a9519e669d51a9174fb3581f986be30f9c Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 19:22:27 +0100 Subject: [PATCH 275/826] Fix config --- config.json | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index d1399f2f3f..a96132bd47 100644 --- a/config.json +++ b/config.json @@ -43,9 +43,9 @@ "status": "beta" }, { - "slug": "currency-exchange", - "name": "Currency Exchange", - "uuid": "1335ca33-3af0-4720-bcf2-bfa68ffc6862", + "slug": "electric-bill", + "name": "Electric Bill", + "uuid": "b2fd556b-07a8-47d6-9811-4d847cf0c6da", "concepts": ["numbers"], "prerequisites": ["basics"], "status": "beta" @@ -2103,6 +2103,14 @@ ], "difficulty": 9 }, + { + "slug": "currency-exchange", + "name": "Currency Exchange", + "uuid": "1335ca33-3af0-4720-bcf2-bfa68ffc6862", + "concepts": [], + "prerequisites": [], + "status": "deprecated" + }, { "slug": "accumulate", "name": "Accumulate", From 4de9c1b89663e80516c0e7f532b03b1bc8e830a7 Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 19:25:24 +0100 Subject: [PATCH 276/826] Fixed --- config.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config.json b/config.json index a96132bd47..e292ece5b7 100644 --- a/config.json +++ b/config.json @@ -50,6 +50,14 @@ "prerequisites": ["basics"], "status": "beta" }, + { + "slug": "currency-exchange", + "name": "Currency Exchange", + "uuid": "1335ca33-3af0-4720-bcf2-bfa68ffc6862", + "concepts": [], + "prerequisites": [], + "status": "wip" + }, { "slug": "meltdown-mitigation", "name": "Meltdown Mitigation", @@ -2103,14 +2111,6 @@ ], "difficulty": 9 }, - { - "slug": "currency-exchange", - "name": "Currency Exchange", - "uuid": "1335ca33-3af0-4720-bcf2-bfa68ffc6862", - "concepts": [], - "prerequisites": [], - "status": "deprecated" - }, { "slug": "accumulate", "name": "Accumulate", From 8cf33d44f5f2b35f69bf8580a71212702ad269ac Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 19:27:24 +0100 Subject: [PATCH 277/826] Added introduction --- exercises/concept/electric-bill/.docs/hint.md | 1 + .../electric-bill/.docs/introduction.md | 179 ++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 exercises/concept/electric-bill/.docs/hint.md create mode 100644 exercises/concept/electric-bill/.docs/introduction.md diff --git a/exercises/concept/electric-bill/.docs/hint.md b/exercises/concept/electric-bill/.docs/hint.md new file mode 100644 index 0000000000..1f600ada68 --- /dev/null +++ b/exercises/concept/electric-bill/.docs/hint.md @@ -0,0 +1 @@ +# Todo diff --git a/exercises/concept/electric-bill/.docs/introduction.md b/exercises/concept/electric-bill/.docs/introduction.md new file mode 100644 index 0000000000..2c3863aa58 --- /dev/null +++ b/exercises/concept/electric-bill/.docs/introduction.md @@ -0,0 +1,179 @@ +# About + +Python has three different types of built-in numbers: integers ([`int`][int]), floating-point ([`float`][float]), and complex ([`complex`][complex]). Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][decimals]) are also available via import from the standard library. + +Whole numbers (_including hex, octals and binary numbers_) **without** decimal places are identified as `ints`: + +```python +# Ints are whole numbers. +>>> 1234 +1234 +>>> type(1234) + + +>>> -12 +-12 +``` + +Numbers containing a decimal point (with or without fractional parts) are identified as `floats`: + +```python +>>> 3.45 +3.45 +>>> type(3.45) + +``` + +## Arithmetic + +Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). + +### Addition and subtraction + +Addition and subtraction act like in normal math. +If atleast one of the operands is a `float`, the other will be converted to a `float` as well. +Otherwise both operands will be converted to `ints`: + +```python +>>> 5 - 3 +2 +# The int is widened to a float here, and a float is returned. +>>> 3 + 4.0 +7.0 +``` + +### Multiplication + +As with addition and subtraction, multiplication will convert narrower numbers to match their less narrow counterparts: + +```python +>>> 3 * 2 +6 + +>>> 3 * 2.0 +6.0 +``` + +### Division + +Division always returns a `float`, even if the result is a whole number: + +```python +>>> 6/5 +1.2 + +>>> 6/2 +3.0 +``` + +### Floor division + +If an `int` result is needed, you can use floor division to truncate the result. +Floor division is performed using the `//` operator: + +```python +>>> 6//5 +1 + +>>> 6//2 +3 +``` + +### Modulo + +The modulo operator (`%`) returns the remainder of the division of the two operands: + +```python +>>> 5 % 3 +2 + +>>> 8 % 2 +0 +``` + +### Exponentiation + +Exponentiation is performed using the `**` operator: + +```python +>>> 2 ** 3 +8 + +>>> 4 ** 0.5 +2 +``` + +All numbers (except complex) support all [arithmetic operations][arethmetic-operations], evaluated according to [operator precedence][operator precedence]. Support for mathematical functions (beyond `+`, `-`, `/`) for complex numbers can be found in the [cmath][cmath] module. + +## Conversions + +Numbers can be converted from `int` to `floats` and `floats` to `int` using the built-in functions `int()` and `float()`: + +```python +>>> int(3.45) +3 + +>>> float(3) +3.0 +``` + +## Round + +Python provides a built-in function `round(number, )` to round off a floating point number to a given number of decimal places. +If no number of decimal places is specified, the number is rounded off to the nearest integer and will return an `int`: + +```python +>>> round(3.1415926535, 2) +3.14 + +>>> round(3.1415926535) +3 +``` + +## Priority and parentheses + +Python allows you to use parentheses to group expressions. +This is useful when you want to override the default order of operations. + +```python +>>> 2 + 3 * 4 +14 + +>>> (2 + 3) * 4 +20 +``` + +Python follows the [PEMDAS][operator precedence] rule for operator precedence. +This means calculations within `()` have the highest priority, followed by `**`, then `*`, `/`, `//`, `%`, `+`, and `-`: + +```python +>>> 2 + 3 - 4 * 4 +-11 + +>>> (2 + 3 - 4) * 4 +20 + +# In the following example, the `**` operator has the highest priority, then `*`, then `+` +# Meaning we first do 4 ** 4, then 3 * 64, then 2 + 192 +>>> 2 + 3 * 4 ** 4 +770 +``` + +## Precision & Representation + +Integers in Python have [arbitrary precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) -- the amount of digits is limited only by the available memory of the host system. + +Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. + +For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. + +[int]: https://docs.python.org/3/library/functions.html#int +[float]: https://docs.python.org/3/library/functions.html#float +[complex]: https://docs.python.org/3/library/functions.html#complex +[fractions]: https://docs.python.org/3/library/fractions.html +[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal +[0.30000000000000004.com]: https://0.30000000000000004.com/ +[cmath]: https://docs.python.org/3.9/library/cmath.html +[arethmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence +[floating point math]: https://docs.python.org/3.9/tutorial/floatingpoint.html From 2172e42befaccd2f72b1c6aa4264b5c03acf1c4f Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 19:29:15 +0100 Subject: [PATCH 278/826] Fix --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index e292ece5b7..c7e5b9e080 100644 --- a/config.json +++ b/config.json @@ -54,8 +54,8 @@ "slug": "currency-exchange", "name": "Currency Exchange", "uuid": "1335ca33-3af0-4720-bcf2-bfa68ffc6862", - "concepts": [], - "prerequisites": [], + "concepts": ["numbers"], + "prerequisites": ["basics"], "status": "wip" }, { From 68ec0e47665325ea3a39c83075c94d9ef8036782 Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 19:30:56 +0100 Subject: [PATCH 279/826] fix --- exercises/concept/electric-bill/.docs/{hint.md => hints.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exercises/concept/electric-bill/.docs/{hint.md => hints.md} (100%) diff --git a/exercises/concept/electric-bill/.docs/hint.md b/exercises/concept/electric-bill/.docs/hints.md similarity index 100% rename from exercises/concept/electric-bill/.docs/hint.md rename to exercises/concept/electric-bill/.docs/hints.md From 33fc34c77fbc61de4092035fecf1911d59e8bdff Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Dec 2022 12:10:44 -0800 Subject: [PATCH 280/826] Small Edits for Modulo --- concepts/numbers/about.md | 62 ++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 2c3863aa58..05f214825b 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -1,8 +1,9 @@ # About -Python has three different types of built-in numbers: integers ([`int`][int]), floating-point ([`float`][float]), and complex ([`complex`][complex]). Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][decimals]) are also available via import from the standard library. +Python has three different types of built-in numbers: integers ([`int`][int]), floating-point ([`float`][float]), and complex ([`complex`][complex]). +Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][decimals]) are also available via import from the standard library. -Whole numbers (_including hex, octals and binary numbers_) **without** decimal places are identified as `ints`: +Whole numbers including hexadecimal ([_`hex()`_][hex]), octal ([_`oct()`_][oct]) and binary ([_`bin()`_][bin]) numbers **without** decimal places are also identified as `ints`: ```python # Ints are whole numbers. @@ -28,11 +29,11 @@ Numbers containing a decimal point (with or without fractional parts) are identi Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). + ### Addition and subtraction -Addition and subtraction act like in normal math. -If atleast one of the operands is a `float`, the other will be converted to a `float` as well. -Otherwise both operands will be converted to `ints`: +Addition and subtraction operators behave as they do in normal math. +If one or more of the operands is a `float`, the remaining `int`s will be converted to `float`s as well: ```python >>> 5 - 3 @@ -84,11 +85,29 @@ Floor division is performed using the `//` operator: The modulo operator (`%`) returns the remainder of the division of the two operands: ```python +# The result of % is zero here, because dividing 8 by 2 leaves no remainder +>>> 8 % 2 +0 + + >>> 5 % 3 2 +``` + +Which is equivalent to: + + +```python +>>> whole_part = int(5/3) +1 + +>>> decimal_part = 5/3 - whole_part +0.6666666666666667 + +>>> whole_remainder = decimal_part * 3 +2.0 +``` ->>> 8 % 2 -0 ``` ### Exponentiation @@ -103,12 +122,15 @@ Exponentiation is performed using the `**` operator: 2 ``` -All numbers (except complex) support all [arithmetic operations][arethmetic-operations], evaluated according to [operator precedence][operator precedence]. Support for mathematical functions (beyond `+`, `-`, `/`) for complex numbers can be found in the [cmath][cmath] module. +All numbers (except complex) support all [arithmetic operations][arithmetic-operations], evaluated according to [operator precedence][operator precedence]. +Support for mathematical functions (beyond `+` and `-`) for complex numbers can be found in the [cmath][cmath] module. + ## Conversions Numbers can be converted from `int` to `floats` and `floats` to `int` using the built-in functions `int()` and `float()`: + ```python >>> int(3.45) 3 @@ -117,9 +139,10 @@ Numbers can be converted from `int` to `floats` and `floats` to `int` using the 3.0 ``` + ## Round -Python provides a built-in function `round(number, )` to round off a floating point number to a given number of decimal places. +Python provides a built-in function [`round(number, )`][round] to round off a floating point number to a given number of decimal places. If no number of decimal places is specified, the number is rounded off to the nearest integer and will return an `int`: ```python @@ -163,17 +186,22 @@ This means calculations within `()` have the highest priority, followed by `**`, Integers in Python have [arbitrary precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) -- the amount of digits is limited only by the available memory of the host system. -Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. +Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. +Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. -[int]: https://docs.python.org/3/library/functions.html#int -[float]: https://docs.python.org/3/library/functions.html#float -[complex]: https://docs.python.org/3/library/functions.html#complex -[fractions]: https://docs.python.org/3/library/fractions.html -[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal [0.30000000000000004.com]: https://0.30000000000000004.com/ -[cmath]: https://docs.python.org/3.9/library/cmath.html [arethmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex -[operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence +[bin]: https://docs.python.org/3/library/functions.html#bin +[cmath]: https://docs.python.org/3.9/library/cmath.html +[complex]: https://docs.python.org/3/library/functions.html#complex +[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal +[float]: https://docs.python.org/3/library/functions.html#float [floating point math]: https://docs.python.org/3.9/tutorial/floatingpoint.html +[fractions]: https://docs.python.org/3/library/fractions.html +[hex]: https://docs.python.org/3/library/functions.html#hex +[int]: https://docs.python.org/3/library/functions.html#int +[oct]: https://docs.python.org/3/library/functions.html#oct +[operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence +[round]: https://docs.python.org/3/library/functions.html#round From 7b7ec005e52b16b33c32d06e7feaa56a40c96e84 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Dec 2022 12:22:38 -0800 Subject: [PATCH 281/826] More Modulo --- concepts/numbers/about.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 05f214825b..66eb14801d 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -89,12 +89,12 @@ The modulo operator (`%`) returns the remainder of the division of the two opera >>> 8 % 2 0 - +# The result of % is 2 here, because 3 only goes into 5 once, with 2 left over >>> 5 % 3 2 ``` -Which is equivalent to: +Another way to look at 5 % 3: ```python @@ -108,7 +108,6 @@ Which is equivalent to: 2.0 ``` -``` ### Exponentiation From c8e5a7572fe57ed3d94585b136afc04d42ca5df0 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Dec 2022 12:40:15 -0800 Subject: [PATCH 282/826] added links --- concepts/numbers/about.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 66eb14801d..659d63cb8c 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -165,7 +165,7 @@ This is useful when you want to override the default order of operations. 20 ``` -Python follows the [PEMDAS][operator precedence] rule for operator precedence. +Python follows the [PEMDAS][pemdas] rule for operator precedence. This means calculations within `()` have the highest priority, followed by `**`, then `*`, `/`, `//`, `%`, `+`, and `-`: ```python @@ -191,7 +191,8 @@ Complex numbers have a `real` and an `imaginary` part, both of which are represe For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. [0.30000000000000004.com]: https://0.30000000000000004.com/ -[arethmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[pemdas]: https://mathworld.wolfram.com/PEMDAS.html +[arithmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex [bin]: https://docs.python.org/3/library/functions.html#bin [cmath]: https://docs.python.org/3.9/library/cmath.html [complex]: https://docs.python.org/3/library/functions.html#complex @@ -203,4 +204,4 @@ For a more detailed discussions of the issues and limitations of floating point [int]: https://docs.python.org/3/library/functions.html#int [oct]: https://docs.python.org/3/library/functions.html#oct [operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence -[round]: https://docs.python.org/3/library/functions.html#round +[round]: https://docs.python.org/3/library/functions.html#round \ No newline at end of file From 36ad472fea996dd14995622d039c9bbfa5cd2920 Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 21:41:36 +0100 Subject: [PATCH 283/826] Fix --- concepts/numbers/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/numbers/.meta/config.json b/concepts/numbers/.meta/config.json index 583a8284a8..25c2db7981 100644 --- a/concepts/numbers/.meta/config.json +++ b/concepts/numbers/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "There are three different types of built-in numbers: integers (\"int\"), floating-point (\"float\"), and complex (\"complex\"). Ints have arbitrary precision and floats typically have 15 decimal places of precision -- but both Int and float precision vary by host system. Complex numbers have a real and an imaginary part - each represented by floats.", "authors": ["Ticktakto", "Yabby1997", "limm-jk", "OMEGA-Y", "wnstj2007"], - "contributors": ["BethanyG", "KaiAragaki"] + "contributors": ["BethanyG", "KaiAragaki", "meatball133"] } From 13ee826acd1f7815577d2af7a38b8defb677d3f5 Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 21:48:22 +0100 Subject: [PATCH 284/826] Removed finished code --- exercises/concept/electric-bill/electric_bill.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/concept/electric-bill/electric_bill.py b/exercises/concept/electric-bill/electric_bill.py index e7e90c59c6..0c2938d2dd 100644 --- a/exercises/concept/electric-bill/electric_bill.py +++ b/exercises/concept/electric-bill/electric_bill.py @@ -7,7 +7,7 @@ def get_the_amount_of_hours(hours): :param: hours: int - amount of hours. :return: int - amount of hours. """ - return (hours + 3) % 24 + pass def get_kW_value(watts): @@ -16,7 +16,7 @@ def get_kW_value(watts): :param: watts: int - watt value. :return: float - kW value. """ - return round(watts / 1000, 1) # rounds here + pass def get_kwh_value(watts): @@ -25,7 +25,7 @@ def get_kwh_value(watts): :param: watts: int - watt value. :param: hours: int - kilowatt hour value. """ - return int(get_kW_value(watts) // 3600) + pass def get_efficiency(efficiency): @@ -34,7 +34,7 @@ def get_efficiency(efficiency): :param: efficiency: float - efficiency. :return: float - efficiency. """ - return efficiency / 100 + pass def get_price_of_kwh(watts, efficiency, price): @@ -45,4 +45,4 @@ def get_price_of_kwh(watts, efficiency, price): :param: price: float - price of kWh. :return: float - price of kWh. """ - return price * (get_kwh_value(watts) / get_efficiency(efficiency)) + pass From f03898b0d2235134f63deccf0ff19fb3aaf78277 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Dec 2022 12:54:54 -0800 Subject: [PATCH 285/826] Introduction files Edits Edited concept and exercise introduction.md files. --- concepts/numbers/introduction.md | 97 +------------------ .../electric-bill/.docs/introduction.md | 71 +++++++------- 2 files changed, 38 insertions(+), 130 deletions(-) diff --git a/concepts/numbers/introduction.md b/concepts/numbers/introduction.md index c72139289a..aad55a0e61 100644 --- a/concepts/numbers/introduction.md +++ b/concepts/numbers/introduction.md @@ -1,97 +1,8 @@ # Introduction -## Numbers +Python has three different types of built-in numbers: integers ([`int`][int]), floating-point ([`float`][float]), and complex ([`complex`][complex]). +Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][decimals]) are also available via import from the standard library. -There are three different kinds of built-in numbers in Python : `ints`, `floats`, and `complex`. However, in this exercise you'll be dealing only with `ints` and `floats`. +Whole numbers including hexadecimal ([_`hex()`_][hex]), octal ([_`oct()`_][oct]) and binary ([_`bin()`_][bin]) numbers **without** decimal places are also identified as `ints`. -### ints - -`ints` are whole numbers. e.g. `1234`, `-10`, `20201278`. - -Integers in Python have [arbitrary precision][arbitrary-precision] -- the amount of digits is limited only by the available memory of the host system. - -### floats - -`floats` or `floating point numbers` contain a decimal point. e.g. `0.0`,`3.14`,`-9.01`. - -Floating point numbers are usually implemented in Python using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system and other implementation details. This can create some surprises when working with floats, but is "good enough" for most situations. - -You can see more details and discussions in the following resources: - -- [Python numeric type documentation][numeric-type-docs] -- [The Python Tutorial][floating point math] -- [Documentation for `int()` built in][`int()` built in] -- [Documentation for `float()` built in][`float()` built in] -- [0.30000000000000004.com][0.30000000000000004.com] - -### Precision - -Before diving into arithmetic, it is worth thinking about what precision means. Precision is the level of exactness at which a number can be represented. An `int` is less precise than a `float` in the same way that `1` is less precise than `1.125`. - -## Arithmetic - -Python fully supports arithmetic between `ints` and `floats`. It will convert narrower numbers to match their wider (or more precise) counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). When division with `/`, `//` returns the quotient and `%` returns the remainder. - -Python considers `ints` narrower than `floats`. So, using a float in an expression ensures the result will be a float too. However, when doing division, the result will always be a float, even if only integers are used. - -```python -# The int is widened to a float here, and a float type is returned. ->>> 3 + 4.0 -7.0 ->>> 3 * 4.0 -12.0 ->>> 3 - 2.0 -1.0 -# Division always returns a float. ->>> 6 / 2 -3.0 ->>> 7 / 4 -1.75 -# Calculating remainders. ->>> 7 % 4 -3 ->>> 2 % 4 -2 ->>> 12.75 % 3 -0.75 -``` - -If an int result is needed, you can use `//` to truncate the result. - -```python ->>> 6 // 2 -3 ->>> 7 // 4 -1 -``` - -To convert a float to an integer, you can use `int()`. Also, to convert an integer to a float, you can use `float()`. - -```python ->>> int(6 / 2) -3 ->>> float(1 + 2) -3.0 -``` - -## Underscores in Numeric Literals - -As of version 3.6, Python supports the use of underscores in numerical literals to improve readability: -```python -# A float with underscores ->>> dollars = 35_000_000.0 ->>> print(dollars) -35000000.0 -``` - -The rules for underscores are outline in [pep 515][pep 515] under 'Literal Grammar' are quite dense, but essentially boil down to: -* Underscores can only be between two digits (not at beginning or ends of numbers, or next to signs (+/-) or decimals points) -* No consecutive underscores - -[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic#:~:text=In%20computer%20science%2C%20arbitrary%2Dprecision,memory%20of%20the%20host%20system. -[numeric-type-docs]: https://docs.python.org/3/library/stdtypes.html#typesnumeric -[`int()` built in]: https://docs.python.org/3/library/functions.html#int -[`float()` built in]: https://docs.python.org/3/library/functions.html#float -[0.30000000000000004.com]: https://0.30000000000000004.com/ -[floating point math]: https://docs.python.org/3.9/tutorial/floatingpoint.html -[pep 515]: https://www.python.org/dev/peps/pep-0515/ +Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). diff --git a/exercises/concept/electric-bill/.docs/introduction.md b/exercises/concept/electric-bill/.docs/introduction.md index 2c3863aa58..c3187bbe7f 100644 --- a/exercises/concept/electric-bill/.docs/introduction.md +++ b/exercises/concept/electric-bill/.docs/introduction.md @@ -1,8 +1,9 @@ -# About +# Introduction -Python has three different types of built-in numbers: integers ([`int`][int]), floating-point ([`float`][float]), and complex ([`complex`][complex]). Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][decimals]) are also available via import from the standard library. +Python has three different types of built-in numbers: integers ([`int`][int]), floating-point ([`float`][float]), and complex ([`complex`][complex]). +Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][decimals]) are also available via import from the standard library. -Whole numbers (_including hex, octals and binary numbers_) **without** decimal places are identified as `ints`: +Whole numbers including hexadecimal ([_`hex()`_][hex]), octal ([_`oct()`_][oct]) and binary ([_`bin()`_][bin]) numbers **without** decimal places are also identified as `ints`: ```python # Ints are whole numbers. @@ -28,11 +29,11 @@ Numbers containing a decimal point (with or without fractional parts) are identi Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). + ### Addition and subtraction -Addition and subtraction act like in normal math. -If atleast one of the operands is a `float`, the other will be converted to a `float` as well. -Otherwise both operands will be converted to `ints`: +Addition and subtraction operators behave as they do in normal math. +If one or more of the operands is a `float`, the remaining `int`s will be converted to `float`s as well: ```python >>> 5 - 3 @@ -84,42 +85,32 @@ Floor division is performed using the `//` operator: The modulo operator (`%`) returns the remainder of the division of the two operands: ```python ->>> 5 % 3 -2 - +# The result of % is zero here, because dividing 8 by 2 leaves no remainder >>> 8 % 2 0 -``` - -### Exponentiation - -Exponentiation is performed using the `**` operator: -```python ->>> 2 ** 3 -8 - ->>> 4 ** 0.5 +# The result of % is 2 here, because 3 only goes into 5 once, with 2 left over +>>> 5 % 3 2 ``` -All numbers (except complex) support all [arithmetic operations][arethmetic-operations], evaluated according to [operator precedence][operator precedence]. Support for mathematical functions (beyond `+`, `-`, `/`) for complex numbers can be found in the [cmath][cmath] module. - -## Conversions +Another way to look at 5 % 3: -Numbers can be converted from `int` to `floats` and `floats` to `int` using the built-in functions `int()` and `float()`: ```python ->>> int(3.45) -3 +>>> whole_part = int(5/3) +1 ->>> float(3) -3.0 +>>> decimal_part = 5/3 - whole_part +0.6666666666666667 + +>>> whole_remainder = decimal_part * 3 +2.0 ``` ## Round -Python provides a built-in function `round(number, )` to round off a floating point number to a given number of decimal places. +Python provides a built-in function [`round(number, )`][round] to round off a floating point number to a given number of decimal places. If no number of decimal places is specified, the number is rounded off to the nearest integer and will return an `int`: ```python @@ -143,7 +134,7 @@ This is useful when you want to override the default order of operations. 20 ``` -Python follows the [PEMDAS][operator precedence] rule for operator precedence. +Python follows the [PEMDAS][pemdas] rule for operator precedence. This means calculations within `()` have the highest priority, followed by `**`, then `*`, `/`, `//`, `%`, `+`, and `-`: ```python @@ -163,17 +154,23 @@ This means calculations within `()` have the highest priority, followed by `**`, Integers in Python have [arbitrary precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) -- the amount of digits is limited only by the available memory of the host system. -Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. +Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. +Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. -[int]: https://docs.python.org/3/library/functions.html#int -[float]: https://docs.python.org/3/library/functions.html#float -[complex]: https://docs.python.org/3/library/functions.html#complex -[fractions]: https://docs.python.org/3/library/fractions.html -[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal [0.30000000000000004.com]: https://0.30000000000000004.com/ +[pemdas]: https://mathworld.wolfram.com/PEMDAS.html +[arithmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[bin]: https://docs.python.org/3/library/functions.html#bin [cmath]: https://docs.python.org/3.9/library/cmath.html -[arethmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex -[operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence +[complex]: https://docs.python.org/3/library/functions.html#complex +[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal +[float]: https://docs.python.org/3/library/functions.html#float [floating point math]: https://docs.python.org/3.9/tutorial/floatingpoint.html +[fractions]: https://docs.python.org/3/library/fractions.html +[hex]: https://docs.python.org/3/library/functions.html#hex +[int]: https://docs.python.org/3/library/functions.html#int +[oct]: https://docs.python.org/3/library/functions.html#oct +[operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence +[round]: https://docs.python.org/3/library/functions.html#round From f0195b3175ede1d98ad08930d65f79b9fa4bf90b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Dec 2022 13:05:24 -0800 Subject: [PATCH 286/826] Link Adjustmets --- concepts/numbers/about.md | 7 ++++--- concepts/numbers/introduction.md | 10 ++++++++++ exercises/concept/electric-bill/.docs/introduction.md | 8 +++----- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 659d63cb8c..874e600edc 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -183,7 +183,7 @@ This means calculations within `()` have the highest priority, followed by `**`, ## Precision & Representation -Integers in Python have [arbitrary precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) -- the amount of digits is limited only by the available memory of the host system. +Integers in Python have [arbitrary precision][arbitrary-precision] -- the amount of digits is limited only by the available memory of the host system. Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. @@ -191,7 +191,7 @@ Complex numbers have a `real` and an `imaginary` part, both of which are represe For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. [0.30000000000000004.com]: https://0.30000000000000004.com/ -[pemdas]: https://mathworld.wolfram.com/PEMDAS.html +[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic [arithmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex [bin]: https://docs.python.org/3/library/functions.html#bin [cmath]: https://docs.python.org/3.9/library/cmath.html @@ -204,4 +204,5 @@ For a more detailed discussions of the issues and limitations of floating point [int]: https://docs.python.org/3/library/functions.html#int [oct]: https://docs.python.org/3/library/functions.html#oct [operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence -[round]: https://docs.python.org/3/library/functions.html#round \ No newline at end of file +[pemdas]: https://mathworld.wolfram.com/PEMDAS.html +[round]: https://docs.python.org/3/library/functions.html#round diff --git a/concepts/numbers/introduction.md b/concepts/numbers/introduction.md index aad55a0e61..3491bc20a3 100644 --- a/concepts/numbers/introduction.md +++ b/concepts/numbers/introduction.md @@ -6,3 +6,13 @@ Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][ Whole numbers including hexadecimal ([_`hex()`_][hex]), octal ([_`oct()`_][oct]) and binary ([_`bin()`_][bin]) numbers **without** decimal places are also identified as `ints`. Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). + + +[bin]: https://docs.python.org/3/library/functions.html#bin +[complex]: https://docs.python.org/3/library/functions.html#complex +[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal +[float]: https://docs.python.org/3/library/functions.html#float +[fractions]: https://docs.python.org/3/library/fractions.html +[hex]: https://docs.python.org/3/library/functions.html#hex +[int]: https://docs.python.org/3/library/functions.html#int +[oct]: https://docs.python.org/3/library/functions.html#oct diff --git a/exercises/concept/electric-bill/.docs/introduction.md b/exercises/concept/electric-bill/.docs/introduction.md index c3187bbe7f..5b6f2d19da 100644 --- a/exercises/concept/electric-bill/.docs/introduction.md +++ b/exercises/concept/electric-bill/.docs/introduction.md @@ -149,10 +149,11 @@ This means calculations within `()` have the highest priority, followed by `**`, >>> 2 + 3 * 4 ** 4 770 ``` +[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic ## Precision & Representation -Integers in Python have [arbitrary precision](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) -- the amount of digits is limited only by the available memory of the host system. +Integers in Python have [arbitrary precision][arbitrary-precision] -- the amount of digits is limited only by the available memory of the host system. Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. @@ -160,10 +161,7 @@ Complex numbers have a `real` and an `imaginary` part, both of which are represe For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. [0.30000000000000004.com]: https://0.30000000000000004.com/ -[pemdas]: https://mathworld.wolfram.com/PEMDAS.html -[arithmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex [bin]: https://docs.python.org/3/library/functions.html#bin -[cmath]: https://docs.python.org/3.9/library/cmath.html [complex]: https://docs.python.org/3/library/functions.html#complex [decimals]: https://docs.python.org/3/library/decimal.html#module-decimal [float]: https://docs.python.org/3/library/functions.html#float @@ -172,5 +170,5 @@ For a more detailed discussions of the issues and limitations of floating point [hex]: https://docs.python.org/3/library/functions.html#hex [int]: https://docs.python.org/3/library/functions.html#int [oct]: https://docs.python.org/3/library/functions.html#oct -[operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence +[pemdas]: https://mathworld.wolfram.com/PEMDAS.html [round]: https://docs.python.org/3/library/functions.html#round From e1d6ff256ff862d83d615542b9f81acfdf8dc58d Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 22:07:49 +0100 Subject: [PATCH 287/826] Smal changes --- concepts/numbers/about.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 874e600edc..1f894b0aa8 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -29,6 +29,8 @@ Numbers containing a decimal point (with or without fractional parts) are identi Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). +All numbers (except complex) support all [arithmetic operations][arithmetic-operations], evaluated according to [operator precedence][operator precedence]. +Support for mathematical functions (beyond `+` and `-`) for complex numbers can be found in the [cmath][cmath] module. ### Addition and subtraction @@ -96,7 +98,6 @@ The modulo operator (`%`) returns the remainder of the division of the two opera Another way to look at 5 % 3: - ```python >>> whole_part = int(5/3) 1 @@ -108,8 +109,7 @@ Another way to look at 5 % 3: 2.0 ``` - -### Exponentiation +## Exponentiation Exponentiation is performed using the `**` operator: @@ -121,15 +121,10 @@ Exponentiation is performed using the `**` operator: 2 ``` -All numbers (except complex) support all [arithmetic operations][arithmetic-operations], evaluated according to [operator precedence][operator precedence]. -Support for mathematical functions (beyond `+` and `-`) for complex numbers can be found in the [cmath][cmath] module. - - ## Conversions Numbers can be converted from `int` to `floats` and `floats` to `int` using the built-in functions `int()` and `float()`: - ```python >>> int(3.45) 3 @@ -138,7 +133,6 @@ Numbers can be converted from `int` to `floats` and `floats` to `int` using the 3.0 ``` - ## Round Python provides a built-in function [`round(number, )`][round] to round off a floating point number to a given number of decimal places. From 065ef79030d76a5d9f5e2ee0fc5b29f0a9eb89be Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 22:13:30 +0100 Subject: [PATCH 288/826] Fixed capital --- concepts/numbers/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/numbers/.meta/config.json b/concepts/numbers/.meta/config.json index 25c2db7981..7898f6099a 100644 --- a/concepts/numbers/.meta/config.json +++ b/concepts/numbers/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "There are three different types of built-in numbers: integers (\"int\"), floating-point (\"float\"), and complex (\"complex\"). Ints have arbitrary precision and floats typically have 15 decimal places of precision -- but both Int and float precision vary by host system. Complex numbers have a real and an imaginary part - each represented by floats.", + "blurb": "There are three different types of built-in numbers: integers (\"int\"), floating-point (\"float\"), and complex (\"complex\"). Ints have arbitrary precision and floats typically have 15 decimal places of precision -- but both int and float precision vary by host system. Complex numbers have a real and an imaginary part - each represented by floats.", "authors": ["Ticktakto", "Yabby1997", "limm-jk", "OMEGA-Y", "wnstj2007"], "contributors": ["BethanyG", "KaiAragaki", "meatball133"] } From 036ad99247757d2024f7c3ac1e30919f5053c0bf Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 22:24:29 +0100 Subject: [PATCH 289/826] Fixes --- exercises/concept/electric-bill/.docs/introduction.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/exercises/concept/electric-bill/.docs/introduction.md b/exercises/concept/electric-bill/.docs/introduction.md index 5b6f2d19da..1543afe5d5 100644 --- a/exercises/concept/electric-bill/.docs/introduction.md +++ b/exercises/concept/electric-bill/.docs/introduction.md @@ -29,7 +29,6 @@ Numbers containing a decimal point (with or without fractional parts) are identi Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). - ### Addition and subtraction Addition and subtraction operators behave as they do in normal math. @@ -96,7 +95,6 @@ The modulo operator (`%`) returns the remainder of the division of the two opera Another way to look at 5 % 3: - ```python >>> whole_part = int(5/3) 1 @@ -149,7 +147,6 @@ This means calculations within `()` have the highest priority, followed by `**`, >>> 2 + 3 * 4 ** 4 770 ``` -[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic ## Precision & Representation @@ -161,6 +158,7 @@ Complex numbers have a `real` and an `imaginary` part, both of which are represe For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math]. [0.30000000000000004.com]: https://0.30000000000000004.com/ +[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic [bin]: https://docs.python.org/3/library/functions.html#bin [complex]: https://docs.python.org/3/library/functions.html#complex [decimals]: https://docs.python.org/3/library/decimal.html#module-decimal From a170b492a9d4bcba4c1fd78e6e5124512bbade95 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Dec 2022 14:13:35 -0800 Subject: [PATCH 290/826] Hints File and Edits --- .../concept/electric-bill/.docs/hints.md | 28 ++++++++++++- .../electric-bill/.docs/instructions.md | 42 +++++++++---------- .../concept/electric-bill/.meta/config.json | 2 +- .../concept/electric-bill/.meta/exemplar.py | 29 +++++++------ .../concept/electric-bill/electric_bill.py | 18 ++++---- .../electric-bill/electric_bill_test.py | 12 +++--- 6 files changed, 80 insertions(+), 51 deletions(-) diff --git a/exercises/concept/electric-bill/.docs/hints.md b/exercises/concept/electric-bill/.docs/hints.md index 1f600ada68..77aaa50d9c 100644 --- a/exercises/concept/electric-bill/.docs/hints.md +++ b/exercises/concept/electric-bill/.docs/hints.md @@ -1 +1,27 @@ -# Todo +# General + +Remember that you can always reuse/call previously completed functions when writing new ones. + + +## 1. Get extra hours +- This is all about calculating the _remainder_ left after whole division. +- Take a look at [`divmod()`][divmod], and look for an operator that does something similar. + +## 2. Get kW value +- Remember to give [`round()`][round] a number of _decimal places_, or you will get a whole number back as a result. + +## 3. Get kwh value +- The result of dividing an `int` by a `float` is always a `float`. +- To get only an integer value from division, use [_floor_ division][floor], which will truncate the decimal. + +## 4. Get efficiency +- The result of dividing an `int` by a `float` is always a `float`. + +## 5. Get cost +- It might be good to _reuse_ or call other functions you have already completed here. +- The result of dividing an `int` by a `float` is always a `float`. + + +[divmod]: https://docs.python.org/3/library/functions.html#divmod +[floor]: https://docs.python.org/3/glossary.html#term-floor-division +[round]: https://docs.python.org/3/library/functions.html#round diff --git a/exercises/concept/electric-bill/.docs/instructions.md b/exercises/concept/electric-bill/.docs/instructions.md index ee81c2b23f..9c96ee924f 100644 --- a/exercises/concept/electric-bill/.docs/instructions.md +++ b/exercises/concept/electric-bill/.docs/instructions.md @@ -1,17 +1,18 @@ # Instructions -The company you work for want to reduce their carbon footprint, so they wants you to write a program which calculates the power usage of their electronics and the cost of running them. +The company you work for wants to reduce their carbon footprint, so they want you to write a program to calculate the power usage and cost of running their electronics. 1. Get extra hours -Your employer wants a program that calculates the time it takes to run different electronics. -Currently the time is stored in hours. -When your employer added the hours they noticed that the time was not correct and they want you to add 3 extra hours to the time data. -They also would like to know how many hours needs to be removed to get the data in full days(24 hours). -The time given doesn't have to be in full days. +Your employer has a program that calculates the time it takes to run different electronics. +Currently, the time is stored in hours. +When your employer added the hours, they noticed that the time duration was not correct. +They want you to add 3 extra hours to the time data. +They would also like to know how many "extra" hours there are after converting the data to "full" days (a day is 24 hours). +The time to convert may not be in full days. Implement a function `get_extra_hours()` that accepts an integer which holds the number of hours. -The function should then `return` an integer with how many hours which has to be removed to get the time in full days. +The function should make the appropriate "extra hours" adjustment, and then `return` an integer representing how many hours needs to be removed from the total to get the time in "full" days. ```python >>> get_extra_hours(25) @@ -20,14 +21,13 @@ The function should then `return` an integer with how many hours which has to be 2. Get kW value -Your employer wants to know the power usage of the different electronics. -They want to know the power usage in kW. -kW stands for kilowatt, there watts is a unit of power. +Your employer wants to know the power usage of the different electronics in kW. +kW stands for kilowatt, where watts are a unit of power. Kilo in the unit name is a prefix in the metric system meaning 1000. -So 1 kilowatt is equal to 1000 watts. +One kilowatt == 1000 watts. Implement a function `get_kW_value()` that accepts an integer which holds the number of watts. -The function should then `return` the watts as kilowatts rounded to 1 decimal. +The function should then `return` the watts as kilowatts rounded to 1 decimal place. ```python >>> get_kW_value(1150) @@ -36,12 +36,12 @@ The function should then `return` the watts as kilowatts rounded to 1 decimal. 3. Get kwh value -To be able to calculate the cost of running the electronics your employer wants to know the power usage in kWh. -kWh stands for kilowatt-hour, there hour is a unit of time. -So 1 kilowatt-hour is equal to 1000 watts used for 1 hour. -An hour is made of 60 minutes and a minute is made of 60 seconds. -So 1 hour is equal to 3600 seconds. -To get the kWh value you have to have to convert the watts to kW and then floor-divide it by 3600. +To be able to calculate the cost of running the electronics, your employer needs to know the power usage in kWh. +kWh stands for kilowatt-hour, where hour is a unit of time. +One kilowatt-hour == 1000 watts used for 1 hour. +An hour is made up of 60 minutes and a minute is made up of 60 seconds. +One hour is equal to 3600 seconds. +To calculate the kWh value, you must convert watts to kW, and then floor-divide the result by 3600. Implement a function `get_kWh_value()` that accepts an integer which holds the number of watts. The function should then `return` the watts as an integer. @@ -54,8 +54,8 @@ The function should then `return` the watts as an integer. 4. Get efficiency Electronics are not 100% efficient. -Therefore, your employer wants you to calculate the efficiency of the electronics. -To get efficiency you have to divide the power factor (a float between 0 and 100) by 100. +Therefore, your employer wants you to calculate the _efficiency_ of the electronics. +To get efficiency, you must divide the power factor (_a float between 0 and 100_) by 100. Implement a function `get_efficiency()` that accepts a float that holds the power factor. The function should then `return` the calculated efficiency as a float. @@ -71,7 +71,7 @@ Your employer wants to know the cost of running the electronics. The cost of running the electronics is the power used multiplied by the cost per kWh. The power used is the power given divided by the calculated efficiency. -Implement a function `get_cost()` that accepts an integer that holds the number of watts, a float that has the power factor, and a float that holds the cost per kwh. +Implement a function `get_cost(,,)` that accepts an integer that holds the number of watts, a float that has the power factor, and a float that holds the cost per kwh. The function should then `return` the cost of running the electronics as a float. ```python diff --git a/exercises/concept/electric-bill/.meta/config.json b/exercises/concept/electric-bill/.meta/config.json index d1560b2033..eca6e91c08 100644 --- a/exercises/concept/electric-bill/.meta/config.json +++ b/exercises/concept/electric-bill/.meta/config.json @@ -15,5 +15,5 @@ ] }, "icon": "city-office", - "blurb": "to do" + "blurb": "Learn about numbers in Python while saving your employers money on their electric bill." } diff --git a/exercises/concept/electric-bill/.meta/exemplar.py b/exercises/concept/electric-bill/.meta/exemplar.py index 7102c5d6c1..230f892519 100644 --- a/exercises/concept/electric-bill/.meta/exemplar.py +++ b/exercises/concept/electric-bill/.meta/exemplar.py @@ -1,12 +1,13 @@ -"""Functions which helps company to calculate their power usage""" +"""Functions to help the company calculate their power usage.""" -def get_the_amount_of_hours(hours): +def get_extra_hours(hours): """Return the amount of hours. :param: hours: int - amount of hours. - :return: int - amount of hours. + :return: int - amount of "extra" hours. """ + return (hours + 3) % 24 @@ -16,7 +17,9 @@ def get_kW_value(watts): :param: watts: int - watt value. :return: float - kW value. """ - return round(watts / 1000, 1) # rounds here + + # rounds to one decimal place here + return round(watts / 1000, 1) def get_kwh_value(watts): @@ -28,21 +31,21 @@ def get_kwh_value(watts): return get_kW_value(watts) // 3600 -def get_efficiency(efficiency): - """Return the efficiency as a power factor. +def get_efficiency(power_factor): + """Return the efficiency calculated from the power factor. - :param: efficiency: float - efficiency. + :param: power_factor: float. :return: float - efficiency. """ - return efficiency / 100 + return power_factor / 100 -def get_price_of_kwh(watts, efficiency, price): - """Return the price of a given kWh value, efficiency and price. +def get_cost(watts, power_factor, price): + """Calculate the cost of a given kWh value, efficiency and price. :param: watts: int - watt value. - :param: efficiency: float - efficiency. + :param: power_factor: float - efficiency. :param: price: float - price of kWh. - :return: float - price of kWh. + :return: float - cost of kWh. """ - return price * (get_kwh_value(watts) / get_efficiency(efficiency)) + return price * (get_kwh_value(watts) / get_efficiency(power_factor)) diff --git a/exercises/concept/electric-bill/electric_bill.py b/exercises/concept/electric-bill/electric_bill.py index 0c2938d2dd..5a3ba53973 100644 --- a/exercises/concept/electric-bill/electric_bill.py +++ b/exercises/concept/electric-bill/electric_bill.py @@ -1,11 +1,11 @@ """Functions which helps company to calculate their power usage""" -def get_the_amount_of_hours(hours): +def get_extra_hours(hours): """Return the amount of hours. :param: hours: int - amount of hours. - :return: int - amount of hours. + :return: int - amount of "extra" hours. """ pass @@ -28,21 +28,21 @@ def get_kwh_value(watts): pass -def get_efficiency(efficiency): - """Return the efficiency as a power factor. +def get_efficiency(power_factor): + """Return the efficiency calculated from the power factor. - :param: efficiency: float - efficiency. + :param: power_factor: float. :return: float - efficiency. """ pass -def get_price_of_kwh(watts, efficiency, price): - """Return the price of a given kWh value, efficiency and price. +def get_cost(watts, power_factor, price): + """Calculate the cost of a given kWh value, efficiency and price. :param: watts: int - watt value. - :param: efficiency: float - efficiency. + :param: power_factor: float - efficiency. :param: price: float - price of kWh. - :return: float - price of kWh. + :return: float - cost of kWh. """ pass diff --git a/exercises/concept/electric-bill/electric_bill_test.py b/exercises/concept/electric-bill/electric_bill_test.py index 95745e74a8..fdcfe32064 100644 --- a/exercises/concept/electric-bill/electric_bill_test.py +++ b/exercises/concept/electric-bill/electric_bill_test.py @@ -1,23 +1,23 @@ import unittest import pytest -from electric_bill import (get_the_amount_of_hours, +from electric_bill import (get_extra_hours, get_kW_value, get_kwh_value, get_efficiency, - get_price_of_kwh) + get_cost) class LocomotiveEngineerTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_get_the_amount_of_hours(self): + def test_get_extra_hours(self): input_data = [25, 10, 5, 2, 1, 120, 21] output_data = [4, 13, 8, 5, 4, 3, 0] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different value.' - self.assertEqual(get_the_amount_of_hours(input_data), output_data, msg=error_msg) + self.assertEqual(get_extra_hours(input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=2) def test_get_kW_value(self): @@ -48,11 +48,11 @@ def test_get_efficiency(self): self.assertAlmostEqual(get_efficiency(input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=5) - def test_get_price_of_kwh(self): + def test_get_cost(self): input_data = ((5000000, 80.0, 0.25), (2141241, 99.99, 2), (43252135, 0.8, 4), (4321512, 40.0, 2)) output_data = (0.3125, 0, 6000, 5) for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different value.' - self.assertEqual(get_price_of_kwh(*input_data), output_data, msg=error_msg) + self.assertEqual(get_cost(*input_data), output_data, msg=error_msg) From aa638625f3a7c349c8f5da0cfbd12e448618a4a3 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sun, 25 Dec 2022 23:27:05 +0100 Subject: [PATCH 291/826] Fixes --- exercises/concept/electric-bill/.docs/hints.md | 17 ++++++++++------- .../concept/electric-bill/.docs/instructions.md | 6 +++--- .../concept/electric-bill/.docs/introduction.md | 2 +- .../concept/electric-bill/electric_bill.py | 2 +- .../concept/electric-bill/electric_bill_test.py | 4 +++- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/exercises/concept/electric-bill/.docs/hints.md b/exercises/concept/electric-bill/.docs/hints.md index 77aaa50d9c..34a6eb7aa4 100644 --- a/exercises/concept/electric-bill/.docs/hints.md +++ b/exercises/concept/electric-bill/.docs/hints.md @@ -2,26 +2,29 @@ Remember that you can always reuse/call previously completed functions when writing new ones. - ## 1. Get extra hours + - This is all about calculating the _remainder_ left after whole division. - Take a look at [`divmod()`][divmod], and look for an operator that does something similar. -## 2. Get kW value +## 2. Get kW value + - Remember to give [`round()`][round] a number of _decimal places_, or you will get a whole number back as a result. -## 3. Get kwh value +## 3. Get kwh value + - The result of dividing an `int` by a `float` is always a `float`. - To get only an integer value from division, use [_floor_ division][floor], which will truncate the decimal. ## 4. Get efficiency + - The result of dividing an `int` by a `float` is always a `float`. -## 5. Get cost +## 5. Get cost + - It might be good to _reuse_ or call other functions you have already completed here. - The result of dividing an `int` by a `float` is always a `float`. - [divmod]: https://docs.python.org/3/library/functions.html#divmod -[floor]: https://docs.python.org/3/glossary.html#term-floor-division -[round]: https://docs.python.org/3/library/functions.html#round +[floor]: https://docs.python.org/3/glossary.html#term-floor-division +[round]: https://docs.python.org/3/library/functions.html#round diff --git a/exercises/concept/electric-bill/.docs/instructions.md b/exercises/concept/electric-bill/.docs/instructions.md index 9c96ee924f..59aed2919b 100644 --- a/exercises/concept/electric-bill/.docs/instructions.md +++ b/exercises/concept/electric-bill/.docs/instructions.md @@ -34,7 +34,7 @@ The function should then `return` the watts as kilowatts rounded to 1 decimal pl 1.2 ``` -3. Get kwh value +3. Get kWh value To be able to calculate the cost of running the electronics, your employer needs to know the power usage in kWh. kWh stands for kilowatt-hour, where hour is a unit of time. @@ -44,7 +44,7 @@ One hour is equal to 3600 seconds. To calculate the kWh value, you must convert watts to kW, and then floor-divide the result by 3600. Implement a function `get_kWh_value()` that accepts an integer which holds the number of watts. -The function should then `return` the watts as an integer. +The function should then `return` the kilowatt-hours as an integer. ```python >>> get_kWh_value(5000000) @@ -71,7 +71,7 @@ Your employer wants to know the cost of running the electronics. The cost of running the electronics is the power used multiplied by the cost per kWh. The power used is the power given divided by the calculated efficiency. -Implement a function `get_cost(,,)` that accepts an integer that holds the number of watts, a float that has the power factor, and a float that holds the cost per kwh. +Implement a function `get_cost(,,)` that accepts an integer that holds the number of watts, a float that has the power factor, and a float that holds the cost per kWh. The function should then `return` the cost of running the electronics as a float. ```python diff --git a/exercises/concept/electric-bill/.docs/introduction.md b/exercises/concept/electric-bill/.docs/introduction.md index 1543afe5d5..719aea0529 100644 --- a/exercises/concept/electric-bill/.docs/introduction.md +++ b/exercises/concept/electric-bill/.docs/introduction.md @@ -88,7 +88,7 @@ The modulo operator (`%`) returns the remainder of the division of the two opera >>> 8 % 2 0 -# The result of % is 2 here, because 3 only goes into 5 once, with 2 left over +# The result of % is 2 here, because 3 only goes into 5 once, with 2 leftover >>> 5 % 3 2 ``` diff --git a/exercises/concept/electric-bill/electric_bill.py b/exercises/concept/electric-bill/electric_bill.py index 5a3ba53973..2a2dc715fd 100644 --- a/exercises/concept/electric-bill/electric_bill.py +++ b/exercises/concept/electric-bill/electric_bill.py @@ -1,4 +1,4 @@ -"""Functions which helps company to calculate their power usage""" +"""Functions to help the company calculate their power usage.""" def get_extra_hours(hours): diff --git a/exercises/concept/electric-bill/electric_bill_test.py b/exercises/concept/electric-bill/electric_bill_test.py index fdcfe32064..d5963c988f 100644 --- a/exercises/concept/electric-bill/electric_bill_test.py +++ b/exercises/concept/electric-bill/electric_bill_test.py @@ -23,6 +23,7 @@ def test_get_extra_hours(self): def test_get_kW_value(self): input_data = [1000, 2200, 2900, 900, 1160] output_data = [1, 2.2, 2.9, 0.9, 1.2] + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different value.' @@ -32,6 +33,7 @@ def test_get_kW_value(self): def test_get_kwh_value(self): input_data = (5000000, 2141241, 43252135, 5324623462, 4321512) output_data = [1, 0, 12, 1479, 1] + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different value.' @@ -51,7 +53,7 @@ def test_get_efficiency(self): def test_get_cost(self): input_data = ((5000000, 80.0, 0.25), (2141241, 99.99, 2), (43252135, 0.8, 4), (4321512, 40.0, 2)) output_data = (0.3125, 0, 6000, 5) - + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different value.' From 6008ca8da6e8a5d821241319813d5adac9f26dd9 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 26 Dec 2022 00:10:05 +0100 Subject: [PATCH 292/826] Fixes --- .../concept/electric-bill/.docs/instructions.md | 10 +++++----- exercises/concept/electric-bill/.meta/design.md | 12 ++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 exercises/concept/electric-bill/.meta/design.md diff --git a/exercises/concept/electric-bill/.docs/instructions.md b/exercises/concept/electric-bill/.docs/instructions.md index 59aed2919b..03fa156074 100644 --- a/exercises/concept/electric-bill/.docs/instructions.md +++ b/exercises/concept/electric-bill/.docs/instructions.md @@ -2,7 +2,7 @@ The company you work for wants to reduce their carbon footprint, so they want you to write a program to calculate the power usage and cost of running their electronics. -1. Get extra hours +## 1. Get extra hours Your employer has a program that calculates the time it takes to run different electronics. Currently, the time is stored in hours. @@ -19,7 +19,7 @@ The function should make the appropriate "extra hours" adjustment, and then `ret 4 ``` -2. Get kW value +## 2. Get kW value Your employer wants to know the power usage of the different electronics in kW. kW stands for kilowatt, where watts are a unit of power. @@ -34,7 +34,7 @@ The function should then `return` the watts as kilowatts rounded to 1 decimal pl 1.2 ``` -3. Get kWh value +## 3. Get kWh value To be able to calculate the cost of running the electronics, your employer needs to know the power usage in kWh. kWh stands for kilowatt-hour, where hour is a unit of time. @@ -51,7 +51,7 @@ The function should then `return` the kilowatt-hours as an integer. 1 ``` -4. Get efficiency +## 4. Get efficiency Electronics are not 100% efficient. Therefore, your employer wants you to calculate the _efficiency_ of the electronics. @@ -65,7 +65,7 @@ The function should then `return` the calculated efficiency as a float. 0.8 ``` -5. Get cost +## 5. Get cost Your employer wants to know the cost of running the electronics. The cost of running the electronics is the power used multiplied by the cost per kWh. diff --git a/exercises/concept/electric-bill/.meta/design.md b/exercises/concept/electric-bill/.meta/design.md new file mode 100644 index 0000000000..bc4c298dac --- /dev/null +++ b/exercises/concept/electric-bill/.meta/design.md @@ -0,0 +1,12 @@ +# Design + +## Goal + +The goal of this exercise is to teach the student how to use arithmetic operators and type casting between `int` and `float` in Python + +## Learning objectives + +- use `+`, `-`, `*`, `/` to add, subtract, multiply, divide numbers(`int` and `float`). +- use `round()` to round values. +- use `//` to floor divide +- use `%` to calculate remainders. From 644f66b3c0ffb13a62c190ccb313fe1ce4230e21 Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 01:17:23 +0100 Subject: [PATCH 293/826] depricate exercise --- config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index c7e5b9e080..575558b764 100644 --- a/config.json +++ b/config.json @@ -54,9 +54,9 @@ "slug": "currency-exchange", "name": "Currency Exchange", "uuid": "1335ca33-3af0-4720-bcf2-bfa68ffc6862", - "concepts": ["numbers"], - "prerequisites": ["basics"], - "status": "wip" + "concepts": [], + "prerequisites": [], + "status": "deprecated" }, { "slug": "meltdown-mitigation", From 2fe2e6ffc5b0aaf3b242ab4f82ec81d146d359b5 Mon Sep 17 00:00:00 2001 From: Jason Hollis Date: Sun, 25 Dec 2022 22:53:25 -0500 Subject: [PATCH 294/826] Fixed example with wrong answer --- exercises/concept/electric-bill/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/electric-bill/.docs/introduction.md b/exercises/concept/electric-bill/.docs/introduction.md index 719aea0529..02f56f06c2 100644 --- a/exercises/concept/electric-bill/.docs/introduction.md +++ b/exercises/concept/electric-bill/.docs/introduction.md @@ -140,7 +140,7 @@ This means calculations within `()` have the highest priority, followed by `**`, -11 >>> (2 + 3 - 4) * 4 -20 +4 # In the following example, the `**` operator has the highest priority, then `*`, then `+` # Meaning we first do 4 ** 4, then 3 * 64, then 2 + 192 From 145b291ed49fdc4b1197462665e17c35a1e39af5 Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 11:13:19 +0100 Subject: [PATCH 295/826] Fix stuff --- exercises/concept/electric-bill/.meta/exemplar.py | 2 +- exercises/concept/electric-bill/electric_bill.py | 2 +- exercises/concept/electric-bill/electric_bill_test.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/electric-bill/.meta/exemplar.py b/exercises/concept/electric-bill/.meta/exemplar.py index 230f892519..789ffcf395 100644 --- a/exercises/concept/electric-bill/.meta/exemplar.py +++ b/exercises/concept/electric-bill/.meta/exemplar.py @@ -26,7 +26,7 @@ def get_kwh_value(watts): """Return the kWh value of a given watt value and hours. :param: watts: int - watt value. - :param: hours: int - kilowatt hour value. + :return: int - kilowatt hour value. """ return get_kW_value(watts) // 3600 diff --git a/exercises/concept/electric-bill/electric_bill.py b/exercises/concept/electric-bill/electric_bill.py index 2a2dc715fd..cae6af33a2 100644 --- a/exercises/concept/electric-bill/electric_bill.py +++ b/exercises/concept/electric-bill/electric_bill.py @@ -23,7 +23,7 @@ def get_kwh_value(watts): """Return the kWh value of a given watt value and hours. :param: watts: int - watt value. - :param: hours: int - kilowatt hour value. + :return: int - kilowatt hour value. """ pass diff --git a/exercises/concept/electric-bill/electric_bill_test.py b/exercises/concept/electric-bill/electric_bill_test.py index d5963c988f..c6c1611fec 100644 --- a/exercises/concept/electric-bill/electric_bill_test.py +++ b/exercises/concept/electric-bill/electric_bill_test.py @@ -7,7 +7,7 @@ get_cost) -class LocomotiveEngineerTest(unittest.TestCase): +class ElecticBillTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_get_extra_hours(self): From 9d9cc93b637126365d61fcec8c61cc983e124806 Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 11:16:36 +0100 Subject: [PATCH 296/826] Changed to amount --- .../concept/electric-bill/.meta/config.json | 1 + .../concept/electric-bill/.meta/exemplar.py | 16 ++++++++-------- exercises/concept/electric-bill/electric_bill.py | 16 ++++++++-------- .../concept/electric-bill/electric_bill_test.py | 12 ++++++------ 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/exercises/concept/electric-bill/.meta/config.json b/exercises/concept/electric-bill/.meta/config.json index eca6e91c08..752ed40c1a 100644 --- a/exercises/concept/electric-bill/.meta/config.json +++ b/exercises/concept/electric-bill/.meta/config.json @@ -3,6 +3,7 @@ "meatball133", "BethanyG" ], + "contributors": ["MatthijsBlom"], "files": { "solution": [ "electric_bill.py" diff --git a/exercises/concept/electric-bill/.meta/exemplar.py b/exercises/concept/electric-bill/.meta/exemplar.py index 789ffcf395..1b60339011 100644 --- a/exercises/concept/electric-bill/.meta/exemplar.py +++ b/exercises/concept/electric-bill/.meta/exemplar.py @@ -11,22 +11,22 @@ def get_extra_hours(hours): return (hours + 3) % 24 -def get_kW_value(watts): - """Return the kW value of a given watt value. +def get_kW_amount(watts): + """Return the kW amount of a given watt amount. - :param: watts: int - watt value. - :return: float - kW value. + :param: watts: int - watt amount. + :return: float - kW amount. """ # rounds to one decimal place here return round(watts / 1000, 1) -def get_kwh_value(watts): - """Return the kWh value of a given watt value and hours. +def get_kwh_amount(watts): + """Return the kWh amount of a given watt amount and hours. - :param: watts: int - watt value. - :return: int - kilowatt hour value. + :param: watts: int - watt amount. + :return: int - kilowatt hour amount. """ return get_kW_value(watts) // 3600 diff --git a/exercises/concept/electric-bill/electric_bill.py b/exercises/concept/electric-bill/electric_bill.py index cae6af33a2..52a92865a0 100644 --- a/exercises/concept/electric-bill/electric_bill.py +++ b/exercises/concept/electric-bill/electric_bill.py @@ -10,20 +10,20 @@ def get_extra_hours(hours): pass -def get_kW_value(watts): - """Return the kW value of a given watt value. +def get_kW_amount(watts): + """Return the kW amount of a given watt amount. - :param: watts: int - watt value. - :return: float - kW value. + :param: watts: int - watt amount. + :return: float - kW amount. """ pass -def get_kwh_value(watts): - """Return the kWh value of a given watt value and hours. +def get_kwh_amount(watts): + """Return the kWh amount of a given watt amount and hours. - :param: watts: int - watt value. - :return: int - kilowatt hour value. + :param: watts: int - watt amount. + :return: int - kilowatt hour amount. """ pass diff --git a/exercises/concept/electric-bill/electric_bill_test.py b/exercises/concept/electric-bill/electric_bill_test.py index c6c1611fec..dea3b66c15 100644 --- a/exercises/concept/electric-bill/electric_bill_test.py +++ b/exercises/concept/electric-bill/electric_bill_test.py @@ -1,8 +1,8 @@ import unittest import pytest from electric_bill import (get_extra_hours, - get_kW_value, - get_kwh_value, + get_kW_amount, + get_kwh_amount, get_efficiency, get_cost) @@ -20,24 +20,24 @@ def test_get_extra_hours(self): self.assertEqual(get_extra_hours(input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=2) - def test_get_kW_value(self): + def test_get_kW_amount(self): input_data = [1000, 2200, 2900, 900, 1160] output_data = [1, 2.2, 2.9, 0.9, 1.2] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different value.' - self.assertEqual(get_kW_value(input_data), output_data, msg=error_msg) + self.assertEqual(get_kW_amount(input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=3) - def test_get_kwh_value(self): + def test_get_kwh_amount(self): input_data = (5000000, 2141241, 43252135, 5324623462, 4321512) output_data = [1, 0, 12, 1479, 1] for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): error_msg=f'Expected: {output_data} but got a different value.' - self.assertEqual(get_kwh_value(input_data), output_data, msg=error_msg) + self.assertEqual(get_kwh_amount(input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=4) def test_get_efficiency(self): From 9bf352d547d15c505bec44fa82e743d098a89a94 Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 11:19:00 +0100 Subject: [PATCH 297/826] Fix --- exercises/concept/electric-bill/.meta/exemplar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/electric-bill/.meta/exemplar.py b/exercises/concept/electric-bill/.meta/exemplar.py index 1b60339011..2a00a39b9b 100644 --- a/exercises/concept/electric-bill/.meta/exemplar.py +++ b/exercises/concept/electric-bill/.meta/exemplar.py @@ -28,7 +28,7 @@ def get_kwh_amount(watts): :param: watts: int - watt amount. :return: int - kilowatt hour amount. """ - return get_kW_value(watts) // 3600 + return get_kW_amount(watts) // 3600 def get_efficiency(power_factor): @@ -48,4 +48,4 @@ def get_cost(watts, power_factor, price): :param: price: float - price of kWh. :return: float - cost of kWh. """ - return price * (get_kwh_value(watts) / get_efficiency(power_factor)) + return price * (get_kwh_amount(watts) / get_efficiency(power_factor)) From b4c2a9f446f353343e867fd05965feb48291459c Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 11:23:24 +0100 Subject: [PATCH 298/826] fix --- exercises/concept/electric-bill/electric_bill_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/electric-bill/electric_bill_test.py b/exercises/concept/electric-bill/electric_bill_test.py index dea3b66c15..ffbd2fa5a6 100644 --- a/exercises/concept/electric-bill/electric_bill_test.py +++ b/exercises/concept/electric-bill/electric_bill_test.py @@ -16,7 +16,7 @@ def test_get_extra_hours(self): for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different value.' + error_msg=f'Expected: {output_data} but got a different amount.' self.assertEqual(get_extra_hours(input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=2) @@ -26,7 +26,7 @@ def test_get_kW_amount(self): for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different value.' + error_msg=f'Expected: {output_data} but got a different amount.' self.assertEqual(get_kW_amount(input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=3) @@ -36,7 +36,7 @@ def test_get_kwh_amount(self): for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different value.' + error_msg=f'Expected: {output_data} but got a different amount.' self.assertEqual(get_kwh_amount(input_data), output_data, msg=error_msg) @pytest.mark.task(taskno=4) From 71e353a32f281db01eb9d16f05fa03c7d3eaf93e Mon Sep 17 00:00:00 2001 From: Carl Date: Tue, 27 Dec 2022 00:42:35 +0100 Subject: [PATCH 299/826] Revert number --- concepts/numbers/about.md | 2 +- config.json | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 1f894b0aa8..5171b69354 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -170,7 +170,7 @@ This means calculations within `()` have the highest priority, followed by `**`, 20 # In the following example, the `**` operator has the highest priority, then `*`, then `+` -# Meaning we first do 4 ** 4, then 3 * 64, then 2 + 192 +# Meaning we first do 4 ** 4, then 3 * 256, then 2 + 768 >>> 2 + 3 * 4 ** 4 770 ``` diff --git a/config.json b/config.json index 575558b764..b2222755eb 100644 --- a/config.json +++ b/config.json @@ -46,17 +46,17 @@ "slug": "electric-bill", "name": "Electric Bill", "uuid": "b2fd556b-07a8-47d6-9811-4d847cf0c6da", - "concepts": ["numbers"], - "prerequisites": ["basics"], - "status": "beta" + "concepts": [], + "prerequisites": [], + "status": "deprecated" }, { "slug": "currency-exchange", "name": "Currency Exchange", "uuid": "1335ca33-3af0-4720-bcf2-bfa68ffc6862", - "concepts": [], - "prerequisites": [], - "status": "deprecated" + "concepts": ["numbers"], + "prerequisites": ["basics"], + "status": "beta" }, { "slug": "meltdown-mitigation", From 92b57da775356996bc164962307f87ba0fca26f6 Mon Sep 17 00:00:00 2001 From: Carl Date: Tue, 27 Dec 2022 21:48:54 +0100 Subject: [PATCH 300/826] minor improvments --- .../practice/dnd-character/.meta/template.j2 | 18 +++++++-------- .../practice/dnd-character/dnd_character.py | 4 ++++ .../dnd-character/dnd_character_test.py | 22 ++++++++----------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/exercises/practice/dnd-character/.meta/template.j2 b/exercises/practice/dnd-character/.meta/template.j2 index 25642bb760..473af1609a 100644 --- a/exercises/practice/dnd-character/.meta/template.j2 +++ b/exercises/practice/dnd-character/.meta/template.j2 @@ -1,9 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{% set class = exercise | camel_case -%} {{ macros.header(["Character", "modifier"]) }} -class {{ exercise | camel_case }}Test(unittest.TestCase): - {% for supercase in cases -%} +{% macro test_case(supercase) -%} {% set property = supercase["property"] -%} {% set description = supercase["description"] | to_snake -%} {% if "cases" in supercase -%} @@ -17,7 +15,7 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% elif property == "ability" -%} def test_{{ description }}(self): score = Character().{{ property }}() - self.assertIs({{ supercase["expected"] | replace("&&","and") }}, True) + self.assertTrue({{ supercase["expected"] | replace("&&","and") }}) {% elif property == "character" -%} def test_{{ description}}(self): @@ -27,15 +25,17 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% if ability == "hitpoints" -%} {% set statement = statement | replace("constitution","Char.constitution") -%} {%- endif -%} - self.assertIs({{ statement }}, True) + self.assertTrue({{ statement }}) {% endfor %} {% elif property == "strength" -%} def test_{{ description }}(self): Char = Character() - self.assertIs({{ supercase["expected"] | replace(property , ["Char.", property]|join(""))}}, True) - + self.assertTrue({{ supercase["expected"] | replace(property , ["Char.", property]|join(""))}}) {%- endif -%} - {% endfor %} +{%- endmacro %} -{{ macros.footer() }} +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for supercase in cases -%} + {{ test_case(supercase) }} + {% endfor %} \ No newline at end of file diff --git a/exercises/practice/dnd-character/dnd_character.py b/exercises/practice/dnd-character/dnd_character.py index f8ecd30bb8..acfe6aef06 100644 --- a/exercises/practice/dnd-character/dnd_character.py +++ b/exercises/practice/dnd-character/dnd_character.py @@ -1,3 +1,7 @@ class Character: def __init__(self): pass + + +def modifier(value): + pass diff --git a/exercises/practice/dnd-character/dnd_character_test.py b/exercises/practice/dnd-character/dnd_character_test.py index b3a93e5759..d79074ca3a 100644 --- a/exercises/practice/dnd-character/dnd_character_test.py +++ b/exercises/practice/dnd-character/dnd_character_test.py @@ -59,22 +59,18 @@ def test_ability_modifier_for_score_18_is_4(self): def test_random_ability_is_within_range(self): score = Character().ability() - self.assertIs(score >= 3 and score <= 18, True) + self.assertTrue(score >= 3 and score <= 18) def test_random_character_is_valid(self): Char = Character() - self.assertIs(Char.strength >= 3 and Char.strength <= 18, True) - self.assertIs(Char.dexterity >= 3 and Char.dexterity <= 18, True) - self.assertIs(Char.constitution >= 3 and Char.constitution <= 18, True) - self.assertIs(Char.intelligence >= 3 and Char.intelligence <= 18, True) - self.assertIs(Char.wisdom >= 3 and Char.wisdom <= 18, True) - self.assertIs(Char.charisma >= 3 and Char.charisma <= 18, True) - self.assertIs(Char.hitpoints == 10 + modifier(Char.constitution), True) + self.assertTrue(Char.strength >= 3 and Char.strength <= 18) + self.assertTrue(Char.dexterity >= 3 and Char.dexterity <= 18) + self.assertTrue(Char.constitution >= 3 and Char.constitution <= 18) + self.assertTrue(Char.intelligence >= 3 and Char.intelligence <= 18) + self.assertTrue(Char.wisdom >= 3 and Char.wisdom <= 18) + self.assertTrue(Char.charisma >= 3 and Char.charisma <= 18) + self.assertTrue(Char.hitpoints == 10 + modifier(Char.constitution)) def test_each_ability_is_only_calculated_once(self): Char = Character() - self.assertIs(Char.strength == Char.strength, True) - - -if __name__ == "__main__": - unittest.main() + self.assertTrue(Char.strength == Char.strength) From 6841758d29a757cfd10a2f2b2dab272e97dc9fca Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 28 Dec 2022 14:26:16 +0100 Subject: [PATCH 301/826] Reversed some changes --- exercises/practice/dnd-character/dnd_character.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/exercises/practice/dnd-character/dnd_character.py b/exercises/practice/dnd-character/dnd_character.py index acfe6aef06..f8ecd30bb8 100644 --- a/exercises/practice/dnd-character/dnd_character.py +++ b/exercises/practice/dnd-character/dnd_character.py @@ -1,7 +1,3 @@ class Character: def __init__(self): pass - - -def modifier(value): - pass From 6b8b69de4f5816bd68dedfd56dee26ee2f53a958 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Dec 2022 12:46:09 -0800 Subject: [PATCH 302/826] Apply suggestions from code review Reverting to `assertIs`. --- exercises/practice/dnd-character/.meta/template.j2 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/dnd-character/.meta/template.j2 b/exercises/practice/dnd-character/.meta/template.j2 index 473af1609a..adb1565053 100644 --- a/exercises/practice/dnd-character/.meta/template.j2 +++ b/exercises/practice/dnd-character/.meta/template.j2 @@ -15,7 +15,7 @@ {% elif property == "ability" -%} def test_{{ description }}(self): score = Character().{{ property }}() - self.assertTrue({{ supercase["expected"] | replace("&&","and") }}) + self.assertIs({{ supercase["expected"] | replace("&&","and") }}, True) {% elif property == "character" -%} def test_{{ description}}(self): @@ -25,13 +25,13 @@ {% if ability == "hitpoints" -%} {% set statement = statement | replace("constitution","Char.constitution") -%} {%- endif -%} - self.assertTrue({{ statement }}) + self.assertIs({{ statement }}, True) {% endfor %} {% elif property == "strength" -%} def test_{{ description }}(self): Char = Character() - self.assertTrue({{ supercase["expected"] | replace(property , ["Char.", property]|join(""))}}) + self.assertIs({{ supercase["expected"] | replace(property , ["Char.", property]|join(""))}}, True) {%- endif -%} {%- endmacro %} From 01d9cafa04e71cea70f7eed2ff34d8ed9c195049 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Dec 2022 12:52:29 -0800 Subject: [PATCH 303/826] Regenerated Test File --- .../dnd-character/dnd_character_test.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/exercises/practice/dnd-character/dnd_character_test.py b/exercises/practice/dnd-character/dnd_character_test.py index d79074ca3a..3f370bc4bd 100644 --- a/exercises/practice/dnd-character/dnd_character_test.py +++ b/exercises/practice/dnd-character/dnd_character_test.py @@ -59,18 +59,18 @@ def test_ability_modifier_for_score_18_is_4(self): def test_random_ability_is_within_range(self): score = Character().ability() - self.assertTrue(score >= 3 and score <= 18) + self.assertIs(score >= 3 and score <= 18, True) def test_random_character_is_valid(self): Char = Character() - self.assertTrue(Char.strength >= 3 and Char.strength <= 18) - self.assertTrue(Char.dexterity >= 3 and Char.dexterity <= 18) - self.assertTrue(Char.constitution >= 3 and Char.constitution <= 18) - self.assertTrue(Char.intelligence >= 3 and Char.intelligence <= 18) - self.assertTrue(Char.wisdom >= 3 and Char.wisdom <= 18) - self.assertTrue(Char.charisma >= 3 and Char.charisma <= 18) - self.assertTrue(Char.hitpoints == 10 + modifier(Char.constitution)) + self.assertIs(Char.strength >= 3 and Char.strength <= 18, True) + self.assertIs(Char.dexterity >= 3 and Char.dexterity <= 18, True) + self.assertIs(Char.constitution >= 3 and Char.constitution <= 18, True) + self.assertIs(Char.intelligence >= 3 and Char.intelligence <= 18, True) + self.assertIs(Char.wisdom >= 3 and Char.wisdom <= 18, True) + self.assertIs(Char.charisma >= 3 and Char.charisma <= 18, True) + self.assertIs(Char.hitpoints == 10 + modifier(Char.constitution), True) def test_each_ability_is_only_calculated_once(self): Char = Character() - self.assertTrue(Char.strength == Char.strength) + self.assertIs(Char.strength == Char.strength, True) From 2983d723ddc9ec9cde4268295741e6d76cd0ee3b Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 21:57:36 +0100 Subject: [PATCH 304/826] Fixed spacing --- exercises/practice/collatz-conjecture/.meta/template.j2 | 4 ++-- .../practice/collatz-conjecture/collatz_conjecture_test.py | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/exercises/practice/collatz-conjecture/.meta/template.j2 b/exercises/practice/collatz-conjecture/.meta/template.j2 index e8b36f8b4d..4dcbae8550 100644 --- a/exercises/practice/collatz-conjecture/.meta/template.j2 +++ b/exercises/practice/collatz-conjecture/.meta/template.j2 @@ -3,12 +3,12 @@ def test_{{ case["description"] | to_snake }}(self): {% set expected = case["expected"] -%} {% set exp_error = expected["error"] -%} - {%- if case is error_case %} + {%- if case is error_case -%} with self.assertRaises(ValueError) as err: {{ case["property"] }}({{ case["input"]["number"] }}) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "{{ exp_error }}") - {% else %} + {%- else -%} self.assertEqual( {{ case["property"] }}({{ case["input"]["number"] }}), {{ case["expected"] }} diff --git a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py index c11e246b54..b9c6df93c8 100644 --- a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py +++ b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py @@ -9,30 +9,24 @@ class CollatzConjectureTest(unittest.TestCase): def test_zero_steps_for_one(self): - self.assertEqual(steps(1), 0) def test_divide_if_even(self): - self.assertEqual(steps(16), 4) def test_even_and_odd_steps(self): - self.assertEqual(steps(12), 9) def test_large_number_of_even_and_odd_steps(self): - self.assertEqual(steps(1000000), 152) def test_zero_is_an_error(self): - with self.assertRaises(ValueError) as err: steps(0) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "Only positive integers are allowed") def test_negative_value_is_an_error(self): - with self.assertRaises(ValueError) as err: steps(-15) self.assertEqual(type(err.exception), ValueError) From 5d384f236d21e330ea90c9e155c7120b8944a97a Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 19:05:25 +0100 Subject: [PATCH 305/826] Spell fix --- .../practice/leap/.approaches/datetime-addition/content.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/leap/.approaches/datetime-addition/content.md b/exercises/practice/leap/.approaches/datetime-addition/content.md index 3748fa47ca..681e19101a 100644 --- a/exercises/practice/leap/.approaches/datetime-addition/content.md +++ b/exercises/practice/leap/.approaches/datetime-addition/content.md @@ -17,9 +17,9 @@ This approach may be considered a "cheat" for this exercise. By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. If it is the 29th, then the function returns `True` for the year being a leap year. -- A new [datetime][datetime] object is created for Febuary 28th of the year. +- A new [datetime][datetime] object is created for February 28th of the year. - Then the [timedelta][timedelta] of one day is added to that `datetime`, -and the function returns if the [day][day] property of the resulting `datetime` object is the 29th. + and the function returns if the [day][day] property of the resulting `datetime` object is the 29th. [timedelta]: https://docs.python.org/3/library/datetime.html#timedelta-objects [day]: https://docs.python.org/3/library/datetime.html#datetime.datetime.day From ef7760ce2296fdb4a198ac544bc5c625de54b0a5 Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 28 Dec 2022 17:42:17 +0100 Subject: [PATCH 306/826] Start --- .../practice/hamming/.approaches/config.json | 29 ++++++++ .../hamming/.approaches/introduction.md | 67 +++++++++++++++++++ .../hamming/.approaches/range/content.md | 38 +++++++++++ .../hamming/.approaches/sum/content.md | 55 +++++++++++++++ .../hamming/.approaches/zip/content.md | 47 +++++++++++++ 5 files changed, 236 insertions(+) create mode 100644 exercises/practice/hamming/.approaches/config.json create mode 100644 exercises/practice/hamming/.approaches/introduction.md create mode 100644 exercises/practice/hamming/.approaches/range/content.md create mode 100644 exercises/practice/hamming/.approaches/sum/content.md create mode 100644 exercises/practice/hamming/.approaches/zip/content.md diff --git a/exercises/practice/hamming/.approaches/config.json b/exercises/practice/hamming/.approaches/config.json new file mode 100644 index 0000000000..9b5ee619e1 --- /dev/null +++ b/exercises/practice/hamming/.approaches/config.json @@ -0,0 +1,29 @@ +{ + "introduction": { + "authors": ["meatball133", "bethanyg"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "9e0baea1-8b3b-46be-8d26-e247b6a59eee", + "slug": "range", + "title": "Range", + "blurb": "Use range to compare 2 strings", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "75883c53-cc84-483a-8375-eb9375a5f739", + "slug": "zip", + "title": "Zip", + "blurb": "Use zip to compare 2 strings", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "9db3f200-2816-40a8-b660-a6bf87abe470", + "slug": "sum", + "title": "Sum", + "blurb": "Use sum to compare 2 strings", + "authors": ["meatball133", "bethanyg"] + } + ] +} diff --git a/exercises/practice/hamming/.approaches/introduction.md b/exercises/practice/hamming/.approaches/introduction.md new file mode 100644 index 0000000000..88419a879b --- /dev/null +++ b/exercises/practice/hamming/.approaches/introduction.md @@ -0,0 +1,67 @@ +# Introduction + +There are various ways to solve Hamming. +One approach is to iterate over either a range of indexs or to use [zip][zip]. +Another appraoch is to use the range of indexs. + +## General guidance + +The goal of this exercise is to compare two DNA strands and count how many of the nucleotides are different from their equivalent in the other string. +The most common way is to use some kind of loop to iterate over the two strands and compare the nucleotides which has the same index. + +## Approach: Iterating over a range of indexes + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + count = 0 + for x in range(len(strand_a)): + if strand_a[x] != strand_b[x]: + count += 1 + return count +``` + +For more information, check the [range approach][approach-range]. + +## Approach: Iterating with zip + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + count = 0 + for a, b in zip(strand_a, strand_b): + if a != b: + count += 1 + return count +``` + +For more information, check the [zip approach][approach-zip]. + +## Approach: Using sum + +With zip: + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + return sum(a != b for a, b in zip(strand_a, strand_b)) +``` + +With range: + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + return sum(strand_a[index] != strand_b[index] for index in range(len(strand_a))) +``` + +For more information, check the [sum approach][approach-sum]. + +[zip]: https://docs.python.org/3/library/functions.html#zip +[approach-range]: https://exercism.org/tracks/python/exercises/hamming/approaches/range +[approach-sum]: https://exercism.org/tracks/python/exercises/hamming/approaches/sum +[approach-zip]: https://exercism.org/tracks/python/exercises/hamming/approaches/zip diff --git a/exercises/practice/hamming/.approaches/range/content.md b/exercises/practice/hamming/.approaches/range/content.md new file mode 100644 index 0000000000..00c633066f --- /dev/null +++ b/exercises/practice/hamming/.approaches/range/content.md @@ -0,0 +1,38 @@ +# range + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + count = 0 + for index in range(len(strand_a)): + if strand_a[index] != strand_b[index]: + count += 1 + return count +``` + +This approach starts by checking if the two strands are of equal length. +If not, a [`ValueError`][value-error] is raised. + +After that is checked, a variable `count` is initialized to 0. +The count variable will be used to keep track of the number of differences between the two strands. + +[`range()`][range] in Python is a built-in function that returns a sequence of numbers. +Range is an infinite sequence, but it can be limited by providing a `stop` argument. +The `range()` function can also take a `start` argument and a `step` argument. +The inputs are built up like this: `range(, stop, )`. +Since we are only providing a `stop` argument, the `start` argument defaults to 0 and the `step` argument defaults to 1. + +We use range to iterate over the indexes of the `strand_a` string. +We do that by passing the length of the string to the `range()` function by using [`len()`][len]. +The iteration gives us the index of the character in the string. +We then use that index to access the character in the string. + +Then we compare the character at the index in `strand_a` to the character at the same index in `strand_b`. +If they are not equal, we increment the `count` variable by 1. + +After the loop is finished, we return the `count` variable. + +[len]: https://docs.python.org/3/library/functions.html?#len +[range]: https://docs.python.org/3/library/stdtypes.html?#range +[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError diff --git a/exercises/practice/hamming/.approaches/sum/content.md b/exercises/practice/hamming/.approaches/sum/content.md new file mode 100644 index 0000000000..7d03212c52 --- /dev/null +++ b/exercises/practice/hamming/.approaches/sum/content.md @@ -0,0 +1,55 @@ +# sum + +The benefit of using [`sum()`][sum] is that we can use a list comprehension to create a list of booleans. +Then we can pass that list to `sum()` and it will add up all the booleans. +Where `True` is treated as 1 and `False` is treated as 0. +Then that sum is returned. + +This can make the code a bit more concise. + +Here is an example with using `sum()` and `zip()`: + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + return sum(nucleotide_a != nucleotide_b for nucleotide_a, nucleotide_b in zip(strand_a, strand_b)) +``` + +This approach starts by checking if the two strands are of equal length by using [`len()`][len]. +If not, a [`ValueError`][value-error] is raised. + +After that is checked, a variable `count` is initialized to 0. +The count variable will be used to keep track of the number of differences between the two strands. + +This approach uses the [`zip()`][zip] function to iterate over two strings. +You can read more about how to solve this exercise with `zip()` in the [zip approach][approach-zip]. + +What differs in this approach is that we use a list comprehension to create a list of booleans. +The list comprehension iterates over the tuples returned by `zip()`. +Then under the iteration so are the tuples unpacked into two variables, `nucleotide_a` and `nucleotide_b`. +We then compare the characters `nucleotide_a` and `nucleotide_b`. +If they are not equal, we add `True` to the list. +If they are equal, we add `False` to the list. +The list comprehension is then passed to the `sum()` function. + +The [`sum()`][sum] function will add up all the booleans in the list. +Where `True` is treated as 1 and `False` is treated as 0. +You can read more about this behavior in [Boolean as numbers][booleans]. +Then that sum is returned. + +This approach is also doable with range but it is a bit more verbose: + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + return sum(strand_a[index] != strand_b[index] for index in range(len(strand_a))) +``` + +[booleans]: https://realpython.com/python-boolean/#python-booleans-as-numbers +[len]: https://docs.python.org/3/library/functions.html?#len +[sum]: https://docs.python.org/3/library/functions.html?#sum +[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError +[zip]: https://docs.python.org/3.3/library/functions.html#zip +[approach-zip]: https://exercism.org/tracks/python/exercises/hamming/approaches/zip diff --git a/exercises/practice/hamming/.approaches/zip/content.md b/exercises/practice/hamming/.approaches/zip/content.md new file mode 100644 index 0000000000..e8bfab7d09 --- /dev/null +++ b/exercises/practice/hamming/.approaches/zip/content.md @@ -0,0 +1,47 @@ +# zip + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + count = 0 + for nucleotide_a, nucleotide_b in zip(strand_a, strand_b): + if nucleotide_a != nucleotide_b: + count += 1 + return count +``` + +This approach starts by checking if the two strands are of equal length by using [`len()`][len]. +If not, a [`ValueError`][value-error] is raised. + +After that is checked, a variable `count` is initialized to 0. +The count variable will be used to keep track of the number of differences between the two strands. + +We use [`zip()`][zip] to iterate over the characters in `strand_a` and `strand_b` simultaneously. +`zip()` is a built in function. +It takes any number of iterables and returns an iterator of tuples. +Where the i-th tuple contains the i-th element from each of the argument iterables. +For example, the first tuple will contain the first element from each iterable, the second tuple will contain the second element from each iterable, and so on until the shortest iterable is exhausted. + +In python are strings are iterables. + +Here is an example of using `zip()` to iterate over two strings: + +```python +>>> zipped = zip("GGACGG", "AGGACG") +>>> list(zipped) +[('G', 'A'), ('G', 'G'), ('A', 'G'), ('C', 'A'), ('G', 'C'), ('G', 'G')] +``` + +We then use the `zip()` iterator to iterate over the tuples. +We unpack the tuple into two variables, `nucleotide_a` and `nucleotide_b`. +You can read more about unpacking in the concept [concept:python/unpacking-and-multiple-assignment](). + +We then compare the characters `nucleotide_a` and `nucleotide_b`. +If they are not equal, we increment the `count` variable by 1. + +After the loop is finished, we return the `count` variable. + +[len]: https://docs.python.org/3/library/functions.html?#len +[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError +[zip]: https://docs.python.org/3.3/library/functions.html#zip From d043098b04689f5dc97f489165c2dd25be0289b7 Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 28 Dec 2022 17:43:32 +0100 Subject: [PATCH 307/826] Fix variable naming --- exercises/practice/hamming/.approaches/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/hamming/.approaches/introduction.md b/exercises/practice/hamming/.approaches/introduction.md index 88419a879b..f6cd3f77cd 100644 --- a/exercises/practice/hamming/.approaches/introduction.md +++ b/exercises/practice/hamming/.approaches/introduction.md @@ -31,8 +31,8 @@ def distance(strand_a, strand_b): if len(strand_a) != len(strand_b): raise ValueError("Strands must be of equal length.") count = 0 - for a, b in zip(strand_a, strand_b): - if a != b: + for nucleotide_a, nucleotide_b in zip(strand_a, strand_b): + if nucleotide_a != nucleotide_b: count += 1 return count ``` @@ -47,7 +47,7 @@ With zip: def distance(strand_a, strand_b): if len(strand_a) != len(strand_b): raise ValueError("Strands must be of equal length.") - return sum(a != b for a, b in zip(strand_a, strand_b)) + return sum(nucleotide_a != nucleotide_b for nucleotide_a, nucleotide_b in zip(strand_a, strand_b)) ``` With range: From 2ac003c060e87fed3dba0b6575b60680588cae84 Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 28 Dec 2022 17:46:18 +0100 Subject: [PATCH 308/826] Add missing files --- exercises/practice/hamming/.approaches/range/snippet.txt | 8 ++++++++ exercises/practice/hamming/.approaches/sum/snippet.txt | 4 ++++ exercises/practice/hamming/.approaches/zip/snippet.txt | 8 ++++++++ 3 files changed, 20 insertions(+) create mode 100644 exercises/practice/hamming/.approaches/range/snippet.txt create mode 100644 exercises/practice/hamming/.approaches/sum/snippet.txt create mode 100644 exercises/practice/hamming/.approaches/zip/snippet.txt diff --git a/exercises/practice/hamming/.approaches/range/snippet.txt b/exercises/practice/hamming/.approaches/range/snippet.txt new file mode 100644 index 0000000000..85caf5179b --- /dev/null +++ b/exercises/practice/hamming/.approaches/range/snippet.txt @@ -0,0 +1,8 @@ +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + count = 0 + for index in range(len(strand_a)): + if strand_a[index] != strand_b[index]: + count += 1 + return count diff --git a/exercises/practice/hamming/.approaches/sum/snippet.txt b/exercises/practice/hamming/.approaches/sum/snippet.txt new file mode 100644 index 0000000000..d8cc2f6393 --- /dev/null +++ b/exercises/practice/hamming/.approaches/sum/snippet.txt @@ -0,0 +1,4 @@ +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + return sum(nucleotide_a != nucleotide_b for nucleotide_a, nucleotide_b in zip(strand_a, strand_b)) diff --git a/exercises/practice/hamming/.approaches/zip/snippet.txt b/exercises/practice/hamming/.approaches/zip/snippet.txt new file mode 100644 index 0000000000..3c310fe376 --- /dev/null +++ b/exercises/practice/hamming/.approaches/zip/snippet.txt @@ -0,0 +1,8 @@ +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length.") + count = 0 + for nucleotide_a, nucleotide_b in zip(strand_a, strand_b): + if nucleotide_a != nucleotide_b: + count += 1 + return count From 774fa921dabe3e222ab47e004b7135710bdbe700 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 29 Dec 2022 12:08:49 +0100 Subject: [PATCH 309/826] Fixes and added an explainer to why you might want to use that method --- .../hamming/.approaches/introduction.md | 24 ++++++++++++++++--- .../hamming/.approaches/sum/content.md | 2 +- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/exercises/practice/hamming/.approaches/introduction.md b/exercises/practice/hamming/.approaches/introduction.md index f6cd3f77cd..7fa2fff95b 100644 --- a/exercises/practice/hamming/.approaches/introduction.md +++ b/exercises/practice/hamming/.approaches/introduction.md @@ -3,6 +3,7 @@ There are various ways to solve Hamming. One approach is to iterate over either a range of indexs or to use [zip][zip]. Another appraoch is to use the range of indexs. +Some other approaches could be to use enumerate, or filter with lambda. ## General guidance @@ -11,13 +12,19 @@ The most common way is to use some kind of loop to iterate over the two strands ## Approach: Iterating over a range of indexes +Using range is an approach to iterate over a sequence. +Although it is not the most pythonic way, it is a good way to start. +The reasson to use range is that it is a built-in function and it is very fast. +The downside is that it only works with iterators that can be indexed, like lists and strings. +While a built in function like `enumerate()` can take any iterator. + ```python def distance(strand_a, strand_b): if len(strand_a) != len(strand_b): raise ValueError("Strands must be of equal length.") count = 0 - for x in range(len(strand_a)): - if strand_a[x] != strand_b[x]: + for index in range(len(strand_a)): + if strand_a[index] != strand_b[index]: count += 1 return count ``` @@ -26,6 +33,11 @@ For more information, check the [range approach][approach-range]. ## Approach: Iterating with zip +The `zip()` function returns an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc. +The approach to use `zip()` to iterate removes the need to index the iterators. +The downside is that if you need to index the iterators, zip wont work. +Although it is possible to use `enumerate()` with zip to get the index. + ```python def distance(strand_a, strand_b): if len(strand_a) != len(strand_b): @@ -41,6 +53,11 @@ For more information, check the [zip approach][approach-zip]. ## Approach: Using sum +Using `sum()` makes it possible to remove the need for a counter variable. +Since there is no need for a counter variable, the code is more concise. +To make use of sum, so are the examples using a [list comprehension][list-comprehension], although it is not required. +Using sum although requires a bit more knowledge of python compared to the other approaches. + With zip: ```python @@ -61,7 +78,8 @@ def distance(strand_a, strand_b): For more information, check the [sum approach][approach-sum]. -[zip]: https://docs.python.org/3/library/functions.html#zip [approach-range]: https://exercism.org/tracks/python/exercises/hamming/approaches/range [approach-sum]: https://exercism.org/tracks/python/exercises/hamming/approaches/sum [approach-zip]: https://exercism.org/tracks/python/exercises/hamming/approaches/zip +[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[zip]: https://docs.python.org/3/library/functions.html#zip diff --git a/exercises/practice/hamming/.approaches/sum/content.md b/exercises/practice/hamming/.approaches/sum/content.md index 7d03212c52..5254af969f 100644 --- a/exercises/practice/hamming/.approaches/sum/content.md +++ b/exercises/practice/hamming/.approaches/sum/content.md @@ -47,9 +47,9 @@ def distance(strand_a, strand_b): return sum(strand_a[index] != strand_b[index] for index in range(len(strand_a))) ``` +[approach-zip]: https://exercism.org/tracks/python/exercises/hamming/approaches/zip [booleans]: https://realpython.com/python-boolean/#python-booleans-as-numbers [len]: https://docs.python.org/3/library/functions.html?#len [sum]: https://docs.python.org/3/library/functions.html?#sum [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError [zip]: https://docs.python.org/3.3/library/functions.html#zip -[approach-zip]: https://exercism.org/tracks/python/exercises/hamming/approaches/zip From f2716a18e05ac2fee59000ddf44c540da23ae0b4 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 29 Dec 2022 23:21:40 +0100 Subject: [PATCH 310/826] spell fixes --- exercises/practice/hamming/.approaches/introduction.md | 10 +++++----- exercises/practice/hamming/.approaches/sum/content.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/practice/hamming/.approaches/introduction.md b/exercises/practice/hamming/.approaches/introduction.md index 7fa2fff95b..9436d1cc9b 100644 --- a/exercises/practice/hamming/.approaches/introduction.md +++ b/exercises/practice/hamming/.approaches/introduction.md @@ -1,20 +1,20 @@ # Introduction There are various ways to solve Hamming. -One approach is to iterate over either a range of indexs or to use [zip][zip]. -Another appraoch is to use the range of indexs. +One approach is to iterate over either a range of indexes or to use [zip][zip]. +Another approach is to use the range of indexes. Some other approaches could be to use enumerate, or filter with lambda. ## General guidance The goal of this exercise is to compare two DNA strands and count how many of the nucleotides are different from their equivalent in the other string. -The most common way is to use some kind of loop to iterate over the two strands and compare the nucleotides which has the same index. +The most common way is to use some kind of loop to iterate over the two strands and compare the nucleotides with the same index. ## Approach: Iterating over a range of indexes Using range is an approach to iterate over a sequence. Although it is not the most pythonic way, it is a good way to start. -The reasson to use range is that it is a built-in function and it is very fast. +The reason to use range is that it is a built-in function and it is very fast. The downside is that it only works with iterators that can be indexed, like lists and strings. While a built in function like `enumerate()` can take any iterator. @@ -35,7 +35,7 @@ For more information, check the [range approach][approach-range]. The `zip()` function returns an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc. The approach to use `zip()` to iterate removes the need to index the iterators. -The downside is that if you need to index the iterators, zip wont work. +The downside is that if you need to index the iterators, zip won't work. Although it is possible to use `enumerate()` with zip to get the index. ```python diff --git a/exercises/practice/hamming/.approaches/sum/content.md b/exercises/practice/hamming/.approaches/sum/content.md index 5254af969f..f590f14048 100644 --- a/exercises/practice/hamming/.approaches/sum/content.md +++ b/exercises/practice/hamming/.approaches/sum/content.md @@ -7,7 +7,7 @@ Then that sum is returned. This can make the code a bit more concise. -Here is an example with using `sum()` and `zip()`: +Here is an example using `sum()` and `zip()`: ```python def distance(strand_a, strand_b): From 85ef185a521e73b714add30e093f44226905f772 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 3 Jan 2023 16:31:47 -0800 Subject: [PATCH 311/826] Apply suggestions from code review --- .../hamming/.approaches/introduction.md | 52 +++++++++++-------- .../hamming/.approaches/range/content.md | 25 +++++---- .../hamming/.approaches/sum/content.md | 36 ++++++------- .../hamming/.approaches/zip/content.md | 18 +++---- 4 files changed, 70 insertions(+), 61 deletions(-) diff --git a/exercises/practice/hamming/.approaches/introduction.md b/exercises/practice/hamming/.approaches/introduction.md index 9436d1cc9b..8cf91f229e 100644 --- a/exercises/practice/hamming/.approaches/introduction.md +++ b/exercises/practice/hamming/.approaches/introduction.md @@ -3,20 +3,20 @@ There are various ways to solve Hamming. One approach is to iterate over either a range of indexes or to use [zip][zip]. Another approach is to use the range of indexes. -Some other approaches could be to use enumerate, or filter with lambda. +Some other approaches include the use of [`enumerate`][enumerate], or [`filter`][filter] with a [`lambda`][lambda]. ## General guidance The goal of this exercise is to compare two DNA strands and count how many of the nucleotides are different from their equivalent in the other string. -The most common way is to use some kind of loop to iterate over the two strands and compare the nucleotides with the same index. +The most common solution uses some kind of loop to iterate over the two strands and compare nucleotides with the same index. ## Approach: Iterating over a range of indexes -Using range is an approach to iterate over a sequence. -Although it is not the most pythonic way, it is a good way to start. -The reason to use range is that it is a built-in function and it is very fast. -The downside is that it only works with iterators that can be indexed, like lists and strings. -While a built in function like `enumerate()` can take any iterator. +Using [`range`][range] is an approach to iterate over a sequence. +Although it may not be the most _pythonic_ strategy, it is a good way to start. +`range` is a [built-in function][built-in-functions] and it is very fast. +The downside is that `range` only works with [iterators][iterators] that can be indexed, like [concept:python/lists](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range) and [concept:python/strings](https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str). +While the built-in function `enumerate` can take any iterator. ```python def distance(strand_a, strand_b): @@ -33,10 +33,10 @@ For more information, check the [range approach][approach-range]. ## Approach: Iterating with zip -The `zip()` function returns an iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together etc. -The approach to use `zip()` to iterate removes the need to index the iterators. -The downside is that if you need to index the iterators, zip won't work. -Although it is possible to use `enumerate()` with zip to get the index. +The built-in `zip` function returns an iterator of [concept:python/tuples](https://docs.python.org/3.10/tutorial/datastructures.html#tuples-and-sequences) where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together, and so on. +Using `zip()` to iterate removes the need to index into the strands. +The downside is that if you _need to_ index into your iterators, `zip` won't work. +Although it is possible to combine `zip` with `enumerate` to generate indexes. ```python def distance(strand_a, strand_b): @@ -53,27 +53,29 @@ For more information, check the [zip approach][approach-zip]. ## Approach: Using sum -Using `sum()` makes it possible to remove the need for a counter variable. -Since there is no need for a counter variable, the code is more concise. -To make use of sum, so are the examples using a [list comprehension][list-comprehension], although it is not required. -Using sum although requires a bit more knowledge of python compared to the other approaches. +Using the built-in [`sum`][sum] removes the need for a counter variable. +Removing the counter variable makes the code is more concise. +The examples making use of `sum` also use a [generator expression][generator-expression], although that it is not required. +Using `sum` in this fashion requires a bit more Python knowledge compared to the other approaches. -With zip: +With `zip`: ```python def distance(strand_a, strand_b): if len(strand_a) != len(strand_b): raise ValueError("Strands must be of equal length.") - return sum(nucleotide_a != nucleotide_b for nucleotide_a, nucleotide_b in zip(strand_a, strand_b)) + return sum(nucleotide_a != nucleotide_b for + nucleotide_a, nucleotide_b in zip(strand_a, strand_b)) ``` -With range: +With `range`: ```python def distance(strand_a, strand_b): if len(strand_a) != len(strand_b): raise ValueError("Strands must be of equal length.") - return sum(strand_a[index] != strand_b[index] for index in range(len(strand_a))) + return sum(strand_a[index] != strand_b[index] for + index in range(len(strand_a))) ``` For more information, check the [sum approach][approach-sum]. @@ -81,5 +83,13 @@ For more information, check the [sum approach][approach-sum]. [approach-range]: https://exercism.org/tracks/python/exercises/hamming/approaches/range [approach-sum]: https://exercism.org/tracks/python/exercises/hamming/approaches/sum [approach-zip]: https://exercism.org/tracks/python/exercises/hamming/approaches/zip -[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions -[zip]: https://docs.python.org/3/library/functions.html#zip +[built-in-functions]: https://docs.python.org/3.10/library/functions.html +[enumerate]: https://docs.python.org/3.10/library/functions.html#enumerate +[filter]: https://docs.python.org/3.10/library/functions.html#filter +[generator-expression]: https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-generator_expression +[iterators]: https://docs.python.org/3/glossary.html#term-iterator +[lambda]: https://docs.python.org/3/glossary.html#term-lambda +[range]: https://docs.python.org/3.10/library/functions.html#func-range +[sum]: https://docs.python.org/3/library/functions.html#sum +[zip]: https://docs.python.org/3.10/library/functions.html#zip + diff --git a/exercises/practice/hamming/.approaches/range/content.md b/exercises/practice/hamming/.approaches/range/content.md index 00c633066f..3f50c2622a 100644 --- a/exercises/practice/hamming/.approaches/range/content.md +++ b/exercises/practice/hamming/.approaches/range/content.md @@ -14,24 +14,23 @@ def distance(strand_a, strand_b): This approach starts by checking if the two strands are of equal length. If not, a [`ValueError`][value-error] is raised. -After that is checked, a variable `count` is initialized to 0. +After that is checked, a `` variable is initialized to 0. The count variable will be used to keep track of the number of differences between the two strands. -[`range()`][range] in Python is a built-in function that returns a sequence of numbers. -Range is an infinite sequence, but it can be limited by providing a `stop` argument. -The `range()` function can also take a `start` argument and a `step` argument. -The inputs are built up like this: `range(, stop, )`. -Since we are only providing a `stop` argument, the `start` argument defaults to 0 and the `step` argument defaults to 1. +[`range`][range] in Python is a built-in function that returns a sequence of numbers. +`range` produces an infinite sequence, but it can be limited by providing a `` argument. +The `range` function can also take a `` argument and a `` argument. +The inputs are built up like this: `range(, , )`. +When only a `` argument is provided, `` defaults to 0 and `` defaults to 1. -We use range to iterate over the indexes of the `strand_a` string. -We do that by passing the length of the string to the `range()` function by using [`len()`][len]. -The iteration gives us the index of the character in the string. -We then use that index to access the character in the string. +We use `range` to iterate over the indexes of the `strand_a` string. +We do that by passing the length of the string to `range` by using the built-in function [`len`][len]. +Iterating over `range` gives us an index number we can use to access a character in the string. -Then we compare the character at the index in `strand_a` to the character at the same index in `strand_b`. -If they are not equal, we increment the `count` variable by 1. +We can then compare the character at the index in `strand_a` to the character at the same index in `strand_b`. +If the two values are not equal, we increment the `` variable by 1. -After the loop is finished, we return the `count` variable. +After the loop completes, we return the `` variable. [len]: https://docs.python.org/3/library/functions.html?#len [range]: https://docs.python.org/3/library/stdtypes.html?#range diff --git a/exercises/practice/hamming/.approaches/sum/content.md b/exercises/practice/hamming/.approaches/sum/content.md index f590f14048..9123fa9257 100644 --- a/exercises/practice/hamming/.approaches/sum/content.md +++ b/exercises/practice/hamming/.approaches/sum/content.md @@ -1,13 +1,13 @@ # sum -The benefit of using [`sum()`][sum] is that we can use a list comprehension to create a list of booleans. -Then we can pass that list to `sum()` and it will add up all the booleans. +The benefit of using `sum` is that we can use a generator expression to create a list of booleans. +We can then pass that generator to `sum` and it will iterate through and add up all the booleans. Where `True` is treated as 1 and `False` is treated as 0. -Then that sum is returned. +Then that total is returned. This can make the code a bit more concise. -Here is an example using `sum()` and `zip()`: +Here is an example using `sum` with `zip`: ```python def distance(strand_a, strand_b): @@ -16,29 +16,29 @@ def distance(strand_a, strand_b): return sum(nucleotide_a != nucleotide_b for nucleotide_a, nucleotide_b in zip(strand_a, strand_b)) ``` -This approach starts by checking if the two strands are of equal length by using [`len()`][len]. +This approach starts by checking if the two strands are of equal length by using [`len`][len]. If not, a [`ValueError`][value-error] is raised. -After that is checked, a variable `count` is initialized to 0. +After that is checked, a `` variable is initialized to 0. The count variable will be used to keep track of the number of differences between the two strands. -This approach uses the [`zip()`][zip] function to iterate over two strings. -You can read more about how to solve this exercise with `zip()` in the [zip approach][approach-zip]. +This approach uses the [`zip`][zip] function to iterate over two strings. +You can read more about how to solve this exercise with `zip` in the [zip approach][approach-zip]. -What differs in this approach is that we use a list comprehension to create a list of booleans. -The list comprehension iterates over the tuples returned by `zip()`. -Then under the iteration so are the tuples unpacked into two variables, `nucleotide_a` and `nucleotide_b`. -We then compare the characters `nucleotide_a` and `nucleotide_b`. -If they are not equal, we add `True` to the list. -If they are equal, we add `False` to the list. -The list comprehension is then passed to the `sum()` function. +What differs in this approach is that we use a generator expression to create booleans. +The generator expression returns an iterator over the tuples returned by `zip`. +Within the iteration, the tuples are unpacked into two variables, `nucleotide_a` and `nucleotide_b`. +We can then compare `nucleotide_a` and `nucleotide_b`. +If they are **not** equal, `True` is produced. +If they **are** equal, `False` is produced. +The generator expression is then passed to the `sum` function. -The [`sum()`][sum] function will add up all the booleans in the list. +`sum` will then iterate over the generator expression and add up all the booleans. Where `True` is treated as 1 and `False` is treated as 0. You can read more about this behavior in [Boolean as numbers][booleans]. -Then that sum is returned. +Finally the totaled booleans are returned. -This approach is also doable with range but it is a bit more verbose: +This approach is also doable with `range` but it is a bit more verbose: ```python def distance(strand_a, strand_b): diff --git a/exercises/practice/hamming/.approaches/zip/content.md b/exercises/practice/hamming/.approaches/zip/content.md index e8bfab7d09..8b4bdd23bc 100644 --- a/exercises/practice/hamming/.approaches/zip/content.md +++ b/exercises/practice/hamming/.approaches/zip/content.md @@ -11,21 +11,21 @@ def distance(strand_a, strand_b): return count ``` -This approach starts by checking if the two strands are of equal length by using [`len()`][len]. +This approach starts by checking if the two strands are of equal length by using [`len`][len]. If not, a [`ValueError`][value-error] is raised. -After that is checked, a variable `count` is initialized to 0. +After that is checked, a `` variable is initialized to 0. The count variable will be used to keep track of the number of differences between the two strands. -We use [`zip()`][zip] to iterate over the characters in `strand_a` and `strand_b` simultaneously. -`zip()` is a built in function. +We use [`zip`][zip] to iterate over the characters in `strand_a` and `strand_b` simultaneously. +`zip` is a built in function. It takes any number of iterables and returns an iterator of tuples. Where the i-th tuple contains the i-th element from each of the argument iterables. For example, the first tuple will contain the first element from each iterable, the second tuple will contain the second element from each iterable, and so on until the shortest iterable is exhausted. -In python are strings are iterables. +In Python, strings are iterable. -Here is an example of using `zip()` to iterate over two strings: +Here is an example of using `zip` to iterate over two strings: ```python >>> zipped = zip("GGACGG", "AGGACG") @@ -33,14 +33,14 @@ Here is an example of using `zip()` to iterate over two strings: [('G', 'A'), ('G', 'G'), ('A', 'G'), ('C', 'A'), ('G', 'C'), ('G', 'G')] ``` -We then use the `zip()` iterator to iterate over the tuples. +We then use the `zip` iterator to iterate over the tuples. We unpack the tuple into two variables, `nucleotide_a` and `nucleotide_b`. You can read more about unpacking in the concept [concept:python/unpacking-and-multiple-assignment](). We then compare the characters `nucleotide_a` and `nucleotide_b`. -If they are not equal, we increment the `count` variable by 1. +If they are not equal, we increment the count variable by 1. -After the loop is finished, we return the `count` variable. +After the loop is finished, we return the count variable. [len]: https://docs.python.org/3/library/functions.html?#len [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError From 949d146f471e68ecfa75f5b36754b77eaa63fb94 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 3 Jan 2023 16:43:48 -0800 Subject: [PATCH 312/826] Update exercises/practice/hamming/.approaches/introduction.md Co-authored-by: meatball <69751659+meatball133@users.noreply.github.com> --- exercises/practice/hamming/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/hamming/.approaches/introduction.md b/exercises/practice/hamming/.approaches/introduction.md index 8cf91f229e..3272a8bd05 100644 --- a/exercises/practice/hamming/.approaches/introduction.md +++ b/exercises/practice/hamming/.approaches/introduction.md @@ -54,7 +54,7 @@ For more information, check the [zip approach][approach-zip]. ## Approach: Using sum Using the built-in [`sum`][sum] removes the need for a counter variable. -Removing the counter variable makes the code is more concise. +Removing the counter variable makes the code more concise. The examples making use of `sum` also use a [generator expression][generator-expression], although that it is not required. Using `sum` in this fashion requires a bit more Python knowledge compared to the other approaches. From e6c795657f01f5217693927fdfce33b859195145 Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Thu, 29 Dec 2022 22:24:38 -0800 Subject: [PATCH 313/826] Add common exceptions to the TRACEBACK docs. --- docs/TRACEBACKS.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index ddda2eb8bd..05cdc2024c 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -54,6 +54,32 @@ ValueError: not enough values to unpack (expected 2, got 1) Working backwards from the bottom, we see that the call where the exception happened is on line 2 in `my_func`. We got there by calling `my_func` on line 5. +## Common Exceptions + +Python 3.11 defines 67 [built-in exception classes][exception-hierarchy]. +Here is a brief overview of some of the more common exceptions and what they indicate. + +* **SyntaxError**: Python is unable to understand the code as the code has invalid syntax. + For example, there may be an open parenthesis without a matching closing parenthesis. +* **AssertionError**: An `assert` statement (see below) failed. +* **AttributeError**: The code (or unit test!) tried to access the attribute of an object but that object has no attribute. + For example, a unit test excepts a `Robot` object to have a `direction` attribute but when it tried to access `robot.direction`, it does not exist. + This could also indicate a typo, such as using `"Hello".lowercase()` when the correct syntax is `"Hello".lower()`. + `"Hello".lowercase()` raises `AttributeError: 'str' object has no attribute 'lowercase'`. +* **ImportError**: The code tried to import something, but it wasn't there. + For example, a unit test does `from exercise import Thing` but the `exercise.py` file does not define a `Thing`. +* **IndexError**: An invalid index was used to look up a value in a list. + This often indicates the index is not computed properly and is often an off-by-one error. + For example, given `numbers = [1, 2, 3]`, the code `print(numbers[len(numbers)])` will raise an IndexError since `len(numbers)` is 3 but the last valid index is 2. + `[1, 2, 3][3]` raises `IndexError: list index out of range`. +* **KeyError**: Similar to IndexError, this exception is raised when using a key to look up a dictionary value but the key is not set in the dictionary. + For example, `{"Alice": 1}["Bob"]` raises `KeyError: 'Bob'`. +* **TypeError**: Typically, this is raised when the wrong type of data is passed to a function or used in an operation. + For example, `"Hello" + 1` will raise `TypeError: can only concatenate str (not "int") to str. +* **ValueError**: This is usually raised when an invalid value is passed to function. + For example, real square roots only exist for position numbers. + Calling `math.sqrt(-1)` will raise `ValueError: math domain error`. + ## Using the `print` function Sometimes an error is not being raised, but a value is not what is expected. @@ -305,3 +331,4 @@ print(sum) [logging]: https://docs.python.org/3/howto/logging.html [print]: https://www.w3schools.com/python/ref_func_print.asp [pdb]: https://www.geeksforgeeks.org/python-debugger-python-pdb/ +[exception-hierarchy]: https://docs.python.org/3/library/exceptions.html#exception-hierarchy From 11d4006f2904daedbaf4bbfe9438552732ad57f6 Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Thu, 5 Jan 2023 19:52:08 -0800 Subject: [PATCH 314/826] Add examples to all the discussed exceptions --- docs/TRACEBACKS.md | 348 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 326 insertions(+), 22 deletions(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 05cdc2024c..7a2adbf669 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -56,29 +56,333 @@ We got there by calling `my_func` on line 5. ## Common Exceptions -Python 3.11 defines 67 [built-in exception classes][exception-hierarchy]. +Python defines over 60 [built-in exception classes][exception-hierarchy]. Here is a brief overview of some of the more common exceptions and what they indicate. -* **SyntaxError**: Python is unable to understand the code as the code has invalid syntax. - For example, there may be an open parenthesis without a matching closing parenthesis. -* **AssertionError**: An `assert` statement (see below) failed. -* **AttributeError**: The code (or unit test!) tried to access the attribute of an object but that object has no attribute. - For example, a unit test excepts a `Robot` object to have a `direction` attribute but when it tried to access `robot.direction`, it does not exist. - This could also indicate a typo, such as using `"Hello".lowercase()` when the correct syntax is `"Hello".lower()`. - `"Hello".lowercase()` raises `AttributeError: 'str' object has no attribute 'lowercase'`. -* **ImportError**: The code tried to import something, but it wasn't there. - For example, a unit test does `from exercise import Thing` but the `exercise.py` file does not define a `Thing`. -* **IndexError**: An invalid index was used to look up a value in a list. - This often indicates the index is not computed properly and is often an off-by-one error. - For example, given `numbers = [1, 2, 3]`, the code `print(numbers[len(numbers)])` will raise an IndexError since `len(numbers)` is 3 but the last valid index is 2. - `[1, 2, 3][3]` raises `IndexError: list index out of range`. -* **KeyError**: Similar to IndexError, this exception is raised when using a key to look up a dictionary value but the key is not set in the dictionary. - For example, `{"Alice": 1}["Bob"]` raises `KeyError: 'Bob'`. -* **TypeError**: Typically, this is raised when the wrong type of data is passed to a function or used in an operation. - For example, `"Hello" + 1` will raise `TypeError: can only concatenate str (not "int") to str. -* **ValueError**: This is usually raised when an invalid value is passed to function. - For example, real square roots only exist for position numbers. - Calling `math.sqrt(-1)` will raise `ValueError: math domain error`. +### **SyntaxError** + +Python raises a `SyntaxError` when it is unable to understand the code due to invalid syntax. +For example, there may be an open parenthesis without a matching closing parenthesis. + + +
+Click here for a code example. + + +Running this code: + +```python +def distance(strand_a, strand_b): + if len(strand_a) != len(strand_b): + raise ValueError("Strands must be of equal length." # This is missing the closing parenthesis +``` + +will result in a stack trace similar to this (_note the message on the final line_): + +``` +.usr.local.lib.python3.10.site-packages._pytest.python.py:608: in _importtestmodule + mod = import_path(self.path, mode=importmode, root=self.config.rootpath) +.usr.local.lib.python3.10.site-packages._pytest.pathlib.py:533: in import_path + importlib.import_module(module_name) +.usr.local.lib.python3.10.importlib.__init__.py:126: in import_module + return _bootstrap._gcd_import(name[level:], package, level) +:1050: in _gcd_import ??? +:1027: in _find_and_load ??? +:1006: in _find_and_load_unlocked ??? +:688: in _load_unlocked ??? +.usr.local.lib.python3.10.site-packages._pytest.assertion.rewrite.py:168: in exec_module + exec(co, module.__dict__) +.mnt.exercism-iteration.hamming_test.py:3: in + from hamming import ( +E File ".mnt.exercism-iteration.hamming.py", line 10 +E raise ValueError("Strands must be of equal length." +E ^ +E SyntaxError: '(' was never closed +``` + + +
+ + +### **AssertionError** +Python raises an `AssertionError` when an `assert` statement (see below) fails. + + +
+Click here for a code example. + + +Running this code: + +```python +def distance(strand_a, strand_b): + assert len(strand_a) == len(strand_b) + + +distance("ab", "abc") +``` + +will result in a stack trace similar to this (_note the message on the final line_): + +``` +hamming_test.py:3: in + from hamming import ( +hamming.py:5: in + distance("ab", "abc") +hamming.py:2: in distance + assert len(strand_a) == len(strand_b) +E AssertionError +``` + + +
+ + +### **AttributeError** +An `AttributeError` is raised when code (or a unit test!) tries to access the attribute of an object but that object has no such attribute. +For example, a unit test excepts a `Robot` object to have a `direction` attribute but when it tried to access `robot.direction`, it does not exist. + +This could also indicate a typo, such as using `"Hello".lowercase()` when the correct syntax is `"Hello".lower()`. +`"Hello".lowercase()` raises `AttributeError: 'str' object has no attribute 'lowercase'`. + + +
+Click here for a Robot class example. + + +Running this code: + +```python +class Robot: + def __init__(): + #note that there is no self.direction listed here + self.position = (0, 0) + self.orientation = 'SW' + + def forward(): + pass + + +robby = Robot +robby.direction +``` + +will result in a stack trace similar to this (_note the message on the final line_): + +``` +robot_simulator_test.py:3: in + from robot_simulator import ( +robot_simulator.py:12: in + robby.direction +E AttributeError: type object 'Robot' has no attribute 'direction' +``` + + +
+ + +
+Click here for a string example. + + +Running this code: + +```python +def distance(strand_a, strand_b): + if strand_a.lowercase() == strand_b: + return 0 + + +distance("ab", "abc") +``` + +will result in a stack trace similar to this (_note the message on the final line_): + +``` + def distance(strand_a, strand_b): +> if strand_a.lowercase() == strand_b: +E AttributeError: 'str' object has no attribute 'lowercase' +``` + + +
+ + + +### **ImportError** +An `ImportError` is raised when code tries to import something, but Python is unable to do so. +For example, a unit test for `Guidos Gorgeous Lasagna` does `from lasagna import bake_time_remaining`, but the `lasgana.py` solution file might not define `bake_time_remaining`. + + +
+Click here for code example + + +Running the `lasgana.py` file without the defined function would result in the following error: + +```python +We received the following error when we ran your code: + + ImportError while importing test module '.mnt.exercism-iteration.lasagna_test.py'. +Hint: make sure your test modules.packages have valid Python names. + +Traceback: +.mnt.exercism-iteration.lasagna_test.py:6: in + from lasagna import (EXPECTED_BAKE_TIME, +E ImportError: cannot import name 'bake_time_remaining' from 'lasagna' (.mnt.exercism-iteration.lasagna.py) + +During handling of the above exception, another exception occurred: +.usr.local.lib.python3.10.importlib.__init__.py:126: in import_module + return _bootstrap._gcd_import(name[level:], package, level) +.mnt.exercism-iteration.lasagna_test.py:23: in + raise ImportError("In your 'lasagna.py' file, we can not find or import the" + +E ImportError: In your 'lasagna.py' file, we can not find or import the function named 'bake_time_remaining()'. Did you mis-name or forget to define it? +``` + +### **IndexError** + +Python raises an `IndexError` when an invalid index is used to look up a value in a list. +This often indicates the index is not computed properly and is often an off-by-one error. + + +
+Click here for code example + + +Consider the following code. + +```python +def distance(strand_a, strand_b): + same = 0 + for i in range(len(strand_a)): + if strand_a[i] == strand_b[i]: + same += 1 + return same + + +distance("abc", "ab") # Note the first strand is longer than the second strand. +``` + +Running that code will result in an error similar to this one. +(_Note the last line._) + +``` +hamming_test.py:3: in + from hamming import ( +hamming.py:9: in + distance("abc", "ab") # Note the first strand is longer than the second strand. +hamming.py:4: in distance + if strand_a[i] == strand_b[i]: +E IndexError: string index out of range +``` + + +
+ + +### **KeyError** +Similar to `IndexError`, this exception is raised when using a key to look up a dictionary value but the key is not set in the dictionary. + + +
+Click here for code example + + +Consider the following code. + +```python +def to_rna(dna_letter): + translation = {"G": "C", "C": "G", "A": "U", "T": "A"} + return translation[dna_letter] + + +print(to_rna("Q")) # Note, "Q" is not in the translation. +``` + +Running that code will result in an error similar to this one. +(_Note the last line._) + +``` +rna_transcription_test.py:3: in + from rna_transcription import to_rna +rna_transcription.py:6: in + print(to_rna("Q")) +rna_transcription.py:3: in to_rna + return translation[dna_letter] +E KeyError: 'Q' +``` + +
+ + +### **TypeError** +Typically, a `TypeError` is raised when the wrong type of data is passed to a function or used in an operation. + + + +
+Click here for code example + + +Consider the following code. + +```python +def hello(name): # This function expects a string. + return 'Hello, ' + name + '!' + + +print(hello(100)) # 100 is not a string. +``` + +Running that code will result in an error similar to this one. +(_Note the last line._) + +``` +hello_world_test.py:3: in + import hello_world +hello_world.py:5: in + print(hello(100)) +hello_world.py:2: in hello + return 'Hello, ' + name + '!' +E TypeError: can only concatenate str (not "int") to str +``` + + +
+ + +### **ValueError** +A `ValueError` is usually raised when an invalid value is passed to function. +
+Click here for code example + + +Note, real square roots only exist for position numbers. +Calling `math.sqrt(-1)` will raise `ValueError: math domain error` since `-1` is not a valid value for a square root. +In (mathematical) technical terms, -1 is not in the domain of square roots. + + +```python +import math + +math.sqrt(-1) +``` + +Running that code will result in an error similar to this one. +(_Note the last line._) + +``` +square_root_test.py:3: in + from square_root import ( +square_root.py:3: in + math.sqrt(-1) +E ValueError: math domain error +``` + + +
+ ## Using the `print` function @@ -302,7 +606,7 @@ Here is an example of how to use the above debugger commands based on the code e ... ``` -In Python 3.7+ there is an easier way to create breakpoints +In Python 3.7+ there is an easier way to create breakpoints. Simply writing `breakpoint()` where needed will create one. ```python From 6bdb1aede8cf75c240c8d9bee2d0c5cf1b2ce4b0 Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Thu, 5 Jan 2023 20:53:39 -0800 Subject: [PATCH 315/826] Apply suggestions from code review Co-authored-by: BethanyG --- docs/TRACEBACKS.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 7a2adbf669..974b7753d4 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -140,7 +140,7 @@ E AssertionError ### **AttributeError** An `AttributeError` is raised when code (or a unit test!) tries to access the attribute of an object but that object has no such attribute. -For example, a unit test excepts a `Robot` object to have a `direction` attribute but when it tried to access `robot.direction`, it does not exist. +For example, a unit test expects a `Robot` object to have a `direction` attribute, but when it tried to access `robot.direction`, it does not exist. This could also indicate a typo, such as using `"Hello".lowercase()` when the correct syntax is `"Hello".lower()`. `"Hello".lowercase()` raises `AttributeError: 'str' object has no attribute 'lowercase'`. @@ -242,7 +242,7 @@ E ImportError: In your 'lasagna.py' file, we can not find or import the functi ### **IndexError** -Python raises an `IndexError` when an invalid index is used to look up a value in a list. +Python raises an `IndexError` when an invalid index is used to look up a value in a sequence. This often indicates the index is not computed properly and is often an off-by-one error. @@ -354,11 +354,13 @@ E TypeError: can only concatenate str (not "int") to str ### **ValueError** A `ValueError` is usually raised when an invalid value is passed to function. + +
Click here for code example -Note, real square roots only exist for position numbers. +Note, real square roots only exist for positive numbers. Calling `math.sqrt(-1)` will raise `ValueError: math domain error` since `-1` is not a valid value for a square root. In (mathematical) technical terms, -1 is not in the domain of square roots. From 5d9319abe322bfbeb2c70c617f67c6e6398d308f Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Thu, 5 Jan 2023 21:03:49 -0800 Subject: [PATCH 316/826] Update docs/TRACEBACKS.md Co-authored-by: BethanyG --- docs/TRACEBACKS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 974b7753d4..4bdf92cc67 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -238,7 +238,6 @@ During handling of the above exception, another exception occurred: raise ImportError("In your 'lasagna.py' file, we can not find or import the" E ImportError: In your 'lasagna.py' file, we can not find or import the function named 'bake_time_remaining()'. Did you mis-name or forget to define it? -``` ### **IndexError** From ddd0c0aad1f31b654511602ebabf717693ca4805 Mon Sep 17 00:00:00 2001 From: Carl Date: Sat, 31 Dec 2022 15:49:11 +0100 Subject: [PATCH 317/826] Started --- .../.approaches/config.json | 22 ++++ .../.approaches/introduction.md | 120 +++++++++++++++++ .../nested-for-loop-optimized/content.md | 93 +++++++++++++ .../nested-for-loop-optimized/snippet.txt | 5 + .../.approaches/nested-for-loop/content.md | 76 +++++++++++ .../.approaches/nested-for-loop/snippet.txt | 5 + .../palindrome-products/.articles/config.json | 11 ++ .../.articles/performance/code/Benchmark.py | 123 ++++++++++++++++++ .../.articles/performance/content.md | 36 +++++ .../.articles/performance/snippet.md | 6 + 10 files changed, 497 insertions(+) create mode 100644 exercises/practice/palindrome-products/.approaches/config.json create mode 100644 exercises/practice/palindrome-products/.approaches/introduction.md create mode 100644 exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md create mode 100644 exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/snippet.txt create mode 100644 exercises/practice/palindrome-products/.approaches/nested-for-loop/content.md create mode 100644 exercises/practice/palindrome-products/.approaches/nested-for-loop/snippet.txt create mode 100644 exercises/practice/palindrome-products/.articles/config.json create mode 100644 exercises/practice/palindrome-products/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/palindrome-products/.articles/performance/content.md create mode 100644 exercises/practice/palindrome-products/.articles/performance/snippet.md diff --git a/exercises/practice/palindrome-products/.approaches/config.json b/exercises/practice/palindrome-products/.approaches/config.json new file mode 100644 index 0000000000..bcd2aca80d --- /dev/null +++ b/exercises/practice/palindrome-products/.approaches/config.json @@ -0,0 +1,22 @@ +{ + "introduction": { + "authors": ["meatball133", "bethanyg"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "7d26e763-daa0-4396-bc7b-7624fbc2ac5c", + "slug": "nested-for-loop", + "title": "Nested for loop", + "blurb": "Use a nested for loop", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "e6495842-a557-43db-b051-b99ffba03f00", + "slug": "nested-for-loop-optimized", + "title": "Nested for loop optimized", + "blurb": "Nested for loop optimized edition", + "authors": ["meatball133", "bethanyg"] + } + ] +} diff --git a/exercises/practice/palindrome-products/.approaches/introduction.md b/exercises/practice/palindrome-products/.approaches/introduction.md new file mode 100644 index 0000000000..5622723d71 --- /dev/null +++ b/exercises/practice/palindrome-products/.approaches/introduction.md @@ -0,0 +1,120 @@ +# Introduction + +There are various approaches to solving this problem. +These approaches show 2 common ways to solve this problem. +With one being a lot more efficient than the other. +Although with that being said there are other ways to solve this problem. + +## General guidance + +The goal of this exercise is to generate the largest and smallest palindromes from a given range of numbers. + +## Approach: Using a nested for loop + +A nested for loop is a good approach to this problem. +It is simple and easy to understand and it works. + +```python +def largest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + for number_b in range(min_factor, max_factor+1): + if number_a * number_b >= result: + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b > result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if result == 0: + result = None + return (result, answer) + + +def smallest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + for number_b in range(min_factor, max_factor+1): + if number_a * number_b <= result or result == 0: + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b < result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if result == 0: + result = None + return (result, answer) +``` + +For more information, check the [Nested for loop approach][approach-nested-for-loop]. + +## Approach: Using a nested for loop, optimized edition + +This approach is similar to the previous one, but what if I say that with adding a few lines of code, we can make it faster? +Then you might say, how much faster? +Well, for some inputs it reduces the time from 9 minutes to 0.01 seconds. +You can read more about it in the [Performance article][article-performance]. + +```python +def largest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(max_factor, min_factor - 1,-1): + was_bigger = False + for number_b in range(max_factor, number_a - 1, -1): + if number_a * number_b >= result: + was_bigger = True + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b > result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if not was_bigger: + break + if result == 0: + result = None + return (result, answer) + + +def smallest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + was_smaller = False + for number_b in range(min_factor, max_factor+1): + if number_a * number_b <= result or result == 0: + was_smaller = True + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b < result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if not was_smaller: + break + if result == 0: + result = None + return (result, answer) +``` + +You can read more about how to achieve this optimization in: [Nested for loop optimized][approach-nested-for-loop-optimized]. + +## Benchmark + +For more information, check the [Performance article][article-performance]. + +[approach-nested-for-loop]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop +[approach-nested-for-loop-optimized]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop-optimized +[article-performance]: https://exercism.org/tracks/python/exercises/palindrome-products/articles/performance diff --git a/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md new file mode 100644 index 0000000000..9c28157db7 --- /dev/null +++ b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md @@ -0,0 +1,93 @@ +# Nested For Loop Optimized + +The point of this approach is to show with just a few changes, the runtime can be improved by a lot. + +```python +def largest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(max_factor, min_factor - 1,-1): + was_bigger = False + for number_b in range(max_factor, number_a - 1, -1): + if number_a * number_b >= result: + was_bigger = True + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b > result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if not was_bigger: + break + if result == 0: + result = None + return (result, answer) + + +def smallest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + was_smaller = False + for number_b in range(min_factor, max_factor+1): + if number_a * number_b <= result or result == 0: + was_smaller = True + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b < result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if not was_smaller: + break + if result == 0: + result = None + return (result, answer) +``` + +This approach is very similar to the [nested for loop approach][approach-nested-for-loop], but it has a few optimizations that make it faster. +To optimize the largest function, we have to start the inner loop from the maximum factor and go down to the minimum factor. +This way, we can stop the inner loop earlier than before. +We also in the inner loop set the minimum value to the current value of the outer loop. + +Here is an example of how the algorithm work and why we have to modify the loops. +Say we take max to be 99 and min 10. +It would go first round: + +``` +x => [99 , 98, 97 ...] +y => [99] +``` + +And already here we have our result which is `9009[91,99]` +Although the loops has to continue to make us sure there are no higher value. + +``` +x => [98, 97, 96 ...] +y => [99, 98] +... +x => [90, 89, 88 ...] +y => [99, 98,97,96,95,94,93,92,91,90] +``` + +Here we can see that the highest value for this "run" is 90 \* 99 = 8910. +Meaning running beyond this point wont give us any higher value than 9009. + +That is why we introduce the `was_bigger` variable. +With that variable we can check the inner loop if it has been bigger than the current result. +If there has not been a bigger value, we can stop the inner loop and stop the outer loop. +We do that by using the [`break`][break] statement. + +If we hadn't modified the inner loop, it would have started from the minimum factor and gone up to the maximum factor. +This would mean that for every new run in the outer loop, the values would be bigger than the previous run. + +The smallest function is optimized in a similar way as largest function compared to the original approach. +The only difference is that we have to start the inner loop and outer loop from the minimum factor and go up to the maximum factor. +Since what we want is the smallest value, we have to start from the smallest value and go up to the biggest value. + +[approach-nested-for-loop]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop +[break]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops diff --git a/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/snippet.txt b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/snippet.txt new file mode 100644 index 0000000000..bf91dd6753 --- /dev/null +++ b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/snippet.txt @@ -0,0 +1,5 @@ +for number_a in range(max_factor, min_factor - 1,-1): + was_bigger = False + for number_b in range(max_factor, number_a - 1, -1): + if number_a * number_b >= result: + was_bigger = True diff --git a/exercises/practice/palindrome-products/.approaches/nested-for-loop/content.md b/exercises/practice/palindrome-products/.approaches/nested-for-loop/content.md new file mode 100644 index 0000000000..b2fe86d0ac --- /dev/null +++ b/exercises/practice/palindrome-products/.approaches/nested-for-loop/content.md @@ -0,0 +1,76 @@ +# Nested For Loop + +```python +def largest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + for number_b in range(min_factor, max_factor+1): + if number_a * number_b >= result: + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b > result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if result == 0: + result = None + return (result, answer) + + +def smallest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + for number_b in range(min_factor, max_factor+1): + if number_a * number_b <= result or result == 0: + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b < result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if result == 0: + result = None + return (result, answer) +``` + +The largest and smallest functions are very similar. +Although there are some small differences. + +The largest function starts with checking if the min_factor is bigger than the max_factor. +If it is, it raises a [`ValueError`][value-error]. + +After that, it initializes the result to 0 and the answer to an empty list. +Then it starts `for in range` loop. +The first for loop iterates over the range of numbers from min_factor to max_factor. +The second for loop iterates over the same range of numbers. +Then it checks if the product of the two numbers is bigger than the result. +If it is, it converts the product to a string and checks if it is a palindrome. + +We can check if a string is a palindrome by comparing it to its reverse. +There are multiple ways to reverse a string. +The most common way is to use the slice notation. +The slice notation is `string[start:stop:step]`. +You can read more about it in: [reverse string with slice][string-reverse]. + +If it is, it checks if the product is bigger than the result. +If it is, it clears the answer list and sets the result to the product. +Then it appends the two numbers to the answer list. + +After the two for loops, it checks if the result is 0. +If it is, it sets the result to [`None`][none]. +Then it returns the result and the answer. + +The smallest one is very similar. +The differences are that it checks if the product is smaller than the result or if the result is 0. +And that it reset result outside of the if statement. +Instead of inside of it. + +[none]: https://realpython.com/null-in-python/ +[string-reverse]: https://realpython.com/reverse-string-python/#reversing-strings-through-slicing +[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError diff --git a/exercises/practice/palindrome-products/.approaches/nested-for-loop/snippet.txt b/exercises/practice/palindrome-products/.approaches/nested-for-loop/snippet.txt new file mode 100644 index 0000000000..f678cd824a --- /dev/null +++ b/exercises/practice/palindrome-products/.approaches/nested-for-loop/snippet.txt @@ -0,0 +1,5 @@ + for number_a in range(min_factor, max_factor+1): + for number_b in range(min_factor, max_factor+1): + if number_a * number_b >= result: + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: diff --git a/exercises/practice/palindrome-products/.articles/config.json b/exercises/practice/palindrome-products/.articles/config.json new file mode 100644 index 0000000000..2cf84af89d --- /dev/null +++ b/exercises/practice/palindrome-products/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "c90d5c22-3133-4f1b-88b6-3ea9b6df298e", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the performance between different approaches", + "authors": ["meatball133", "bethanyg"] + } + ] +} diff --git a/exercises/practice/palindrome-products/.articles/performance/code/Benchmark.py b/exercises/practice/palindrome-products/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..9bfbbf35b3 --- /dev/null +++ b/exercises/practice/palindrome-products/.articles/performance/code/Benchmark.py @@ -0,0 +1,123 @@ +import timeit +import sys + + +print(sys.version) + + +def largest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + for number_b in range(min_factor, max_factor+1): + if number_a * number_b >= result: + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b > result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if result == 0: + result = None + return (result, answer) + + +def smallest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + for number_b in range(min_factor, max_factor+1): + if number_a * number_b <= result or result == 0: + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b < result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if result == 0: + result = None + return (result, answer) + +def largest_optimized(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(max_factor, min_factor - 1,-1): + was_bigger = False + for number_b in range(max_factor, number_a - 1, -1): + if number_a * number_b >= result: + was_bigger = True + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b > result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if not was_bigger: + break + if result == 0: + result = None + return (result, answer) + + +def smallest_optimized(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + was_smaller = False + for number_b in range(min_factor, max_factor+1): + if number_a * number_b <= result or result == 0: + was_smaller = True + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b < result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if not was_smaller: + break + if result == 0: + result = None + return (result, answer) + + +starttime = timeit.default_timer() +largest(1, 1000) +print("largest, min=1, max=1000 :", timeit.default_timer() - starttime) + +starttime = timeit.default_timer() +largest_optimized(1, 1000) +print("largest_optimized, min=1, max=1000 :", timeit.default_timer() - starttime) + +starttime = timeit.default_timer() +smallest(1, 1000) +print("smallest, min=1, max=1000 :", timeit.default_timer() - starttime) + +starttime = timeit.default_timer() +smallest_optimized(1, 1000) +print("smallest_optimized, min=1, max=1000 :", timeit.default_timer() - starttime) + + + +starttime = timeit.default_timer() +largest(100, 100000) +print("largest, min=100, max=100000 :", timeit.default_timer() - starttime) + +starttime = timeit.default_timer() +largest_optimized(100, 100000) +print("largest_optimized, min=100, max=100000 :", timeit.default_timer() - starttime) + +starttime = timeit.default_timer() +smallest(100, 100000) +print("smallest, min=100, max=100000 :", timeit.default_timer() - starttime) + +starttime = timeit.default_timer() +smallest_optimized(100, 100000) +print("smallest_optimized, min=100, max=100000 :", timeit.default_timer() - starttime) diff --git a/exercises/practice/palindrome-products/.articles/performance/content.md b/exercises/practice/palindrome-products/.articles/performance/content.md new file mode 100644 index 0000000000..f0bce43e18 --- /dev/null +++ b/exercises/practice/palindrome-products/.articles/performance/content.md @@ -0,0 +1,36 @@ +# Performance + +In this approach, we'll find out the performance difference between approaches for palindrome product in Python. + +The [approaches page][approaches] lists two approaches to this exercise: + +1. [Using a nested for loop][approach-nested-for-loop] +2. [Using a nested for loop, optimized edition][approach-nested-for-loop-optimized] + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. +These test were run in windows 11, using Python 3.11.1. + +``` +largest, min=1, max=1000 : 0.05235219991300255 +largest_optimized, min=1, max=1000 : 0.000758000067435205 +smallest, min=1, max=1000 : 0.04553140001371503 +smallest_optimized, min=1, max=1000 : 0.00010269996710121632 + +largest, min=100, max=100000 : 512.5731259000022 +largest_optimized, min=100, max=100000 : 0.013197900028899312 +smallest, min=100, max=100000 : 549.5989698000485 +smallest_optimized, min=100, max=100000 : 0.03933039994444698 +``` + +## Conclusion + +As we can see, the optimized approach is much faster than the original approach. +Although the difference becomes most noticeable when the range is large, the optimized approach is still faster in the small range. + +[approaches]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches +[approach-nested-for-loop]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop +[approach-nested-for-loop-optimized]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop-optimized +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html diff --git a/exercises/practice/palindrome-products/.articles/performance/snippet.md b/exercises/practice/palindrome-products/.articles/performance/snippet.md new file mode 100644 index 0000000000..da2648a6a9 --- /dev/null +++ b/exercises/practice/palindrome-products/.articles/performance/snippet.md @@ -0,0 +1,6 @@ +``` +largest, min=100, max=100000 : 512.5731259000022 +largest_optimized, min=100, max=100000 : 0.013197900028899312 +smallest, min=100, max=100000 : 549.5989698000485 +smallest_optimized, min=100, max=100000 : 0.03933039994444698 +``` From dfd47334201c64edee2540460fb711d356f58e22 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 6 Jan 2023 17:06:21 -0800 Subject: [PATCH 318/826] Apply suggestions from code review Suggestions and nits. --- .../.approaches/introduction.md | 13 +++--- .../nested-for-loop-optimized/content.md | 41 +++++++++---------- .../.approaches/nested-for-loop/content.md | 19 +++++---- .../.articles/performance/content.md | 5 ++- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/exercises/practice/palindrome-products/.approaches/introduction.md b/exercises/practice/palindrome-products/.approaches/introduction.md index 5622723d71..2637f1ae4c 100644 --- a/exercises/practice/palindrome-products/.approaches/introduction.md +++ b/exercises/practice/palindrome-products/.approaches/introduction.md @@ -1,9 +1,8 @@ # Introduction -There are various approaches to solving this problem. -These approaches show 2 common ways to solve this problem. -With one being a lot more efficient than the other. -Although with that being said there are other ways to solve this problem. +There are various ways to solve `palindrome-products`. +This approaches document shows 2 _common_ strategies, with one being a lot more efficient than the other. +That being said, neither approach here is considered canonical, and other "pythonic" approaches could be added/expanded on in the future. ## General guidance @@ -57,9 +56,9 @@ For more information, check the [Nested for loop approach][approach-nested-for-l ## Approach: Using a nested for loop, optimized edition -This approach is similar to the previous one, but what if I say that with adding a few lines of code, we can make it faster? -Then you might say, how much faster? -Well, for some inputs it reduces the time from 9 minutes to 0.01 seconds. +This approach is similar to the previous one, but with the addition of a few lines of code we can make it much faster. +The question then becomes _how much faster_? +Well, for some input sizes it reduces the time from **9 minutes to 0.01 seconds**. You can read more about it in the [Performance article][article-performance]. ```python diff --git a/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md index 9c28157db7..2bc2718391 100644 --- a/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md +++ b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md @@ -1,6 +1,6 @@ # Nested For Loop Optimized -The point of this approach is to show with just a few changes, the runtime can be improved by a lot. +This approach shows that just a few changes can improve the running time of the solution significantly. ```python def largest(min_factor, max_factor): @@ -49,43 +49,42 @@ def smallest(min_factor, max_factor): return (result, answer) ``` -This approach is very similar to the [nested for loop approach][approach-nested-for-loop], but it has a few optimizations that make it faster. -To optimize the largest function, we have to start the inner loop from the maximum factor and go down to the minimum factor. -This way, we can stop the inner loop earlier than before. -We also in the inner loop set the minimum value to the current value of the outer loop. +This approach is very similar to the [nested for loop approach][approach-nested-for-loop], but it has a few optimizations. +To optimize the `largest` function, we have to start the inner loop from the maximum factor and proceed down to the minimum factor. +This allows us to stop the inner loop earlier than before. +We also set the minimum value in the _inner_ loop to the current value of the _outer_ loop. -Here is an example of how the algorithm work and why we have to modify the loops. -Say we take max to be 99 and min 10. -It would go first round: +Here is an example of how the algorithm works and why the loops need to be modified. +Say we take maximum to be 99 and the minimum 10. +In the first round: ``` -x => [99 , 98, 97 ...] -y => [99] +x = [99 , 98, 97 ...] +y = [99] ``` -And already here we have our result which is `9009[91,99]` -Although the loops has to continue to make us sure there are no higher value. +And already we have our result: `9009[91,99]` +Although the loops have to continue to make us sure there are no higher values. ``` -x => [98, 97, 96 ...] -y => [99, 98] +x = [98, 97, 96 ...] +y = [99, 98] ... -x => [90, 89, 88 ...] -y => [99, 98,97,96,95,94,93,92,91,90] -``` +x = [90, 89, 88 ...] +y = [99, 98,97,96,95,94,93,92,91,90] Here we can see that the highest value for this "run" is 90 \* 99 = 8910. -Meaning running beyond this point wont give us any higher value than 9009. +Meaning that running beyond this point won't give us any values higher than 9009. That is why we introduce the `was_bigger` variable. -With that variable we can check the inner loop if it has been bigger than the current result. +With `was_bigger`, we can check if the inner loop has a bigger value than the current result. If there has not been a bigger value, we can stop the inner loop and stop the outer loop. We do that by using the [`break`][break] statement. -If we hadn't modified the inner loop, it would have started from the minimum factor and gone up to the maximum factor. +If we hadn't modified the direction of the inner loop, it would have started from the minimum factor and gone up to the maximum factor. This would mean that for every new run in the outer loop, the values would be bigger than the previous run. -The smallest function is optimized in a similar way as largest function compared to the original approach. +The `smallest` function is optimized in a similar way as `largest` function compared to the original approach. The only difference is that we have to start the inner loop and outer loop from the minimum factor and go up to the maximum factor. Since what we want is the smallest value, we have to start from the smallest value and go up to the biggest value. diff --git a/exercises/practice/palindrome-products/.approaches/nested-for-loop/content.md b/exercises/practice/palindrome-products/.approaches/nested-for-loop/content.md index b2fe86d0ac..5f5086f5b6 100644 --- a/exercises/practice/palindrome-products/.approaches/nested-for-loop/content.md +++ b/exercises/practice/palindrome-products/.approaches/nested-for-loop/content.md @@ -39,38 +39,39 @@ def smallest(min_factor, max_factor): return (result, answer) ``` -The largest and smallest functions are very similar. +The `largest` and `smallest` functions are very similar here. Although there are some small differences. -The largest function starts with checking if the min_factor is bigger than the max_factor. +The `largest` function starts with checking if the min_factor is bigger than the max_factor. If it is, it raises a [`ValueError`][value-error]. After that, it initializes the result to 0 and the answer to an empty list. -Then it starts `for in range` loop. -The first for loop iterates over the range of numbers from min_factor to max_factor. -The second for loop iterates over the same range of numbers. +Then it starts a `for in range` loop. +The outer `for` loop iterates over the range of numbers from min_factor to max_factor. +The inner `for` loop iterates over the same range of numbers. Then it checks if the product of the two numbers is bigger than the result. If it is, it converts the product to a string and checks if it is a palindrome. We can check if a string is a palindrome by comparing it to its reverse. There are multiple ways to reverse a string. -The most common way is to use the slice notation. +The most common way is to use [slice notation][slice-notation]. The slice notation is `string[start:stop:step]`. You can read more about it in: [reverse string with slice][string-reverse]. -If it is, it checks if the product is bigger than the result. -If it is, it clears the answer list and sets the result to the product. +If the string is a palindrome, the solution next checks if the product is bigger than the current result. +If the product is bigger, the answer list is cleared, and the current result is set to the most recent product. Then it appends the two numbers to the answer list. After the two for loops, it checks if the result is 0. If it is, it sets the result to [`None`][none]. Then it returns the result and the answer. -The smallest one is very similar. +The `smallest` one is very similar. The differences are that it checks if the product is smaller than the result or if the result is 0. And that it reset result outside of the if statement. Instead of inside of it. [none]: https://realpython.com/null-in-python/ +[slice-notation]: https://www.learnbyexample.org/python-list-slicing/ [string-reverse]: https://realpython.com/reverse-string-python/#reversing-strings-through-slicing [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError diff --git a/exercises/practice/palindrome-products/.articles/performance/content.md b/exercises/practice/palindrome-products/.articles/performance/content.md index f0bce43e18..f052088335 100644 --- a/exercises/practice/palindrome-products/.articles/performance/content.md +++ b/exercises/practice/palindrome-products/.articles/performance/content.md @@ -1,6 +1,6 @@ # Performance -In this approach, we'll find out the performance difference between approaches for palindrome product in Python. +In this article, we'll examine the performance difference between approaches for `palindrome-products` in Python. The [approaches page][approaches] lists two approaches to this exercise: @@ -10,7 +10,8 @@ The [approaches page][approaches] lists two approaches to this exercise: ## Benchmarks To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. -These test were run in windows 11, using Python 3.11.1. +These tests were run in windows 11, using Python 3.11.1. +Your system results may vary. ``` largest, min=1, max=1000 : 0.05235219991300255 From 6d14e3c949aa6fbe739674baf838ec90d7118a16 Mon Sep 17 00:00:00 2001 From: Matthijs <19817960+MatthijsBlom@users.noreply.github.com> Date: Sat, 7 Jan 2023 20:54:58 +0100 Subject: [PATCH 319/826] Clarify Currency Exchange instructions --- exercises/concept/currency-exchange/.docs/instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/currency-exchange/.docs/instructions.md b/exercises/concept/currency-exchange/.docs/instructions.md index 03f7e6a1e3..3bb6bff971 100644 --- a/exercises/concept/currency-exchange/.docs/instructions.md +++ b/exercises/concept/currency-exchange/.docs/instructions.md @@ -52,8 +52,8 @@ Unfortunately, the booth gets to keep the remainder/change as an added bonus. Create the `get_number_of_bills()` function, taking `budget` and `denomination`. -This function should return the _number of new currency bills_ that you can receive within the given _budget_. -In other words: How many _whole bills_ of new currency fit into the amount of old currency you have in your budget? +This function should return the _number of currency bills_ that you can receive within the given _budget_. +In other words: How many _whole bills_ of currency fit into the amount of currency you have in your budget? Remember -- you can only receive _whole bills_, not fractions of bills, so remember to divide accordingly. Effectively, you are rounding _down_ to the nearest whole bill/denomination. From cac72fbb92f90087d8d87f2c40733c60796572d5 Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 18:55:52 +0100 Subject: [PATCH 320/826] Started --- .../.approaches/config.json | 29 +++++++ .../.approaches/if-else/content.md | 32 ++++++++ .../.approaches/if-else/snippet.txt | 11 +++ .../.approaches/introduction.md | 82 +++++++++++++++++++ .../.approaches/recursion/content.md | 46 +++++++++++ .../.approaches/recursion/snippet.txt | 7 ++ .../.approaches/ternary-operator/content.md | 31 +++++++ .../.approaches/ternary-operator/snippet.txt | 8 ++ .../collatz-conjecture/.articles/config.json | 11 +++ .../.articles/performance/code/Benchmark.py | 43 ++++++++++ .../.articles/performance/content.md | 32 ++++++++ .../.articles/performance/snippet.md | 5 ++ 12 files changed, 337 insertions(+) create mode 100644 exercises/practice/collatz-conjecture/.approaches/config.json create mode 100644 exercises/practice/collatz-conjecture/.approaches/if-else/content.md create mode 100644 exercises/practice/collatz-conjecture/.approaches/if-else/snippet.txt create mode 100644 exercises/practice/collatz-conjecture/.approaches/introduction.md create mode 100644 exercises/practice/collatz-conjecture/.approaches/recursion/content.md create mode 100644 exercises/practice/collatz-conjecture/.approaches/recursion/snippet.txt create mode 100644 exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md create mode 100644 exercises/practice/collatz-conjecture/.approaches/ternary-operator/snippet.txt create mode 100644 exercises/practice/collatz-conjecture/.articles/config.json create mode 100644 exercises/practice/collatz-conjecture/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/collatz-conjecture/.articles/performance/content.md create mode 100644 exercises/practice/collatz-conjecture/.articles/performance/snippet.md diff --git a/exercises/practice/collatz-conjecture/.approaches/config.json b/exercises/practice/collatz-conjecture/.approaches/config.json new file mode 100644 index 0000000000..2ba3bf4c5b --- /dev/null +++ b/exercises/practice/collatz-conjecture/.approaches/config.json @@ -0,0 +1,29 @@ +{ + "introduction": { + "authors": ["bethanyg", "meatball133"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "7b3aeaef-11ef-427c-9086-d2c6d4c022f1", + "slug": "if-else", + "title": "If/Else", + "blurb": "Use if and else", + "authors": ["bethanyg", "meatball133"] + }, + { + "uuid": "a2d8742b-9516-44c8-832d-111874430de0", + "slug": "ternary-operator", + "title": "Ternary operator", + "blurb": "Use a ternary operator", + "authors": ["bethanyg", "meatball133"] + }, + { + "uuid": "01da60d9-c133-41de-90fd-afbba7df2980", + "slug": "recursion", + "title": "Recursion", + "blurb": "Use recursion", + "authors": ["bethanyg", "meatball133"] + } + ] +} diff --git a/exercises/practice/collatz-conjecture/.approaches/if-else/content.md b/exercises/practice/collatz-conjecture/.approaches/if-else/content.md new file mode 100644 index 0000000000..c78fc06a7e --- /dev/null +++ b/exercises/practice/collatz-conjecture/.approaches/if-else/content.md @@ -0,0 +1,32 @@ +# If / Else + +```python +def steps(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + counter = 0 + while number != 1: + if number % 2 == 0: + number /= 2 + else: + number = number * 3 + 1 + counter += 1 + return counter +``` + +This approach starts with checking if the number is less than or equal to zero. +If it is, then it raises a [`ValueError`][value-error]. +After that we declare a counter variable and set it to zero. +Then we start a [`while` loop][while-loop] that will run until the number is equal to one. +Meaning the loop wont run if the number is already one. + +Inside the loop we check if the number is even. +If it is, then we divide it by two. +If it isn't, then we multiply it by three and add one. +After that we increment the counter by one. +After the loop is done, we return the counter variable. + +We have to use a `while` loop here because we don't know how many times the loop will run. + +[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError +[while-loop]: https://realpython.com/python-while-loop/ diff --git a/exercises/practice/collatz-conjecture/.approaches/if-else/snippet.txt b/exercises/practice/collatz-conjecture/.approaches/if-else/snippet.txt new file mode 100644 index 0000000000..eee0d4a3dc --- /dev/null +++ b/exercises/practice/collatz-conjecture/.approaches/if-else/snippet.txt @@ -0,0 +1,11 @@ +def steps(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + counter = 0 + while number != 1: + if number % 2 == 0: + number /= 2 + else: + number = number * 3 + 1 + counter += 1 + return counter diff --git a/exercises/practice/collatz-conjecture/.approaches/introduction.md b/exercises/practice/collatz-conjecture/.approaches/introduction.md new file mode 100644 index 0000000000..20fbe6f90f --- /dev/null +++ b/exercises/practice/collatz-conjecture/.approaches/introduction.md @@ -0,0 +1,82 @@ +# Introduction + +There are various approaches to solving the Collatz Conjecture exercise in Python. +You can for example use a while loop or a recursive function. +You can use if and else statements or the ternary operator. + +## General guidance + +The key to this exercise is to check if the number and then do the correct operation. +Under this process you are suppose to count how many steps it takes to get to one. + +## Approach: If/Else + +This is a good way to solve the exercise, it is easy to understand and it is very readable. +The reason to why you might not want to use this approach is because it is longer than the other approaches. + +```python +def steps(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + counter = 0 + while number != 1: + if number % 2 == 0: + number /= 2 + else: + number = number * 3 + 1 + counter += 1 + return counter +``` + +For more information, check the [if/else approach][approach-if-else]. + +## Approach: Ternary operator + +In this approach we use the ternary operator. +It allows us to write a one-line if and else statement. +This makes the code more concise. + +```python +def steps(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + counter = 0 + while number != 1: + number = number / 2 if number % 2 == 0 else number * 3 + 1 + counter += 1 + return counter +``` + +For more information, check the [Ternary operator approach][approach-ternary-operator]. + +## Approach: Recursive function + +In this approach we use a recursive function. +A recursive function is a function that calls itself. +The reason to why you want to use this approach is because it is more concise than the other approaches. +Since it doesn't need a counter variable. + +The reason to why you might not want to use this approach is that python has a limit on how many times a function can call itself. +This limit is 1000 times. + +```python +def steps(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + if number == 1: + return 0 + number = number / 2 if number % 2 == 0 else number * 3 + 1 + return 1 + steps(number) +``` + +For more information, check the [Recursion approach][approach-recursion]. + +## Benchmarks + +To get a better understanding of the performance of the different approaches, we have created benchmarks. +For more information, check the [Performance article]. + +[performance article]: https://exercism.org/tracks/python/exercises/collatz-conjecture/articles/performance +[approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator +[approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion diff --git a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md new file mode 100644 index 0000000000..b452045ed8 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md @@ -0,0 +1,46 @@ +# Recursion + +```python +def steps(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + if number == 1: + return 0 + number = number / 2 if number % 2 == 0 else number * 3 + 1 + return 1 + steps(number) +``` + +This approach uses [concept:python/recursion]() to solve the problem. +Recursion is a programming technique where a function calls itself. +Recursion is a powerful technique, but can be more tricky. +Recursion isn't that common in Python, it is more common in functional programming languages, like: [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure]. + +This approach starts with checking if the number is less than or equal to zero. +If it is, then it raises a [`ValueError`][value-error]. + +After that we check if the number is equal to one. +If it is, then we return zero. + +Then we use the same ternary operator as in the [ternary operator][ternary-operator] approach. +We declare that number is equal to the result of the ternary operator. +The ternary operator checks if the number is even. +If it is, then we divide it by two. +If it isn't, then we multiply it by three and add one. + +After that we return one plus the result of calling the `steps` function with the new number. +This is the recursion part. + +Doing this exercise with recursion removes the need for a "counter" variable. +Since what we do is that if the number is not one we do `1 + steps(number)`. +Then that function can do the same thing. +Meaning we can get a long chain of `1 + steps(number)` until we reach one and add 0. +That will translate to something like this: `1 + 1 + 1 + 1 + 0`. + +In python we can't have a function call itself more than 1000 times. + +[clojure]: https://exercism.org/tracks/clojure +[elixir]: https://exercism.org/tracks/elixir +[haskell]: https://exercism.org/tracks/haskell +[recursion]: https://realpython.com/python-thinking-recursively/ +[ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator +[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError diff --git a/exercises/practice/collatz-conjecture/.approaches/recursion/snippet.txt b/exercises/practice/collatz-conjecture/.approaches/recursion/snippet.txt new file mode 100644 index 0000000000..addae0aeae --- /dev/null +++ b/exercises/practice/collatz-conjecture/.approaches/recursion/snippet.txt @@ -0,0 +1,7 @@ +def steps(number): + if number < 1: + raise ValueError("Only positive integers are allowed") + if number == 1: + return 0 + number = number / 2 if number % 2 == 0 else number * 3 + 1 + return 1 + steps(number) diff --git a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md new file mode 100644 index 0000000000..518455461c --- /dev/null +++ b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md @@ -0,0 +1,31 @@ +# Ternary operator + +```python +def steps(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + counter = 0 + while number != 1: + number = number / 2 if number % 2 == 0 else number * 3 + 1 + counter += 1 + return counter +``` + +If it is, then it raises a [`ValueError`][value-error]. +After that we declare a counter variable and set it to zero. + +Then we start a `while` loop that will run until the number is equal to one. +Meaning the loop wont run if the number is already one. + +Inside of the loop we have a [ternary operator][ternary-operator]. +The ternary operator is a one-line `if` and `else` statement. +That means that we can make the code more concise by using it. +We declare that number is equal to the result of the ternary operator. +The ternary operator checks if the number is even. +If it is, then we divide it by two. +If it isn't, then we multiply it by three and add one. +After that we increment the counter by one. +After the loop is done, we return the counter variable. + +[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ +[value-error]: https://docs.python.org/3/library/exceptions.html#ValueError diff --git a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/snippet.txt b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/snippet.txt new file mode 100644 index 0000000000..5e6fcd8c03 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/snippet.txt @@ -0,0 +1,8 @@ +def steps(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + counter = 0 + while number != 1: + number = number / 2 if number % 2 == 0 else number * 3 + 1 + counter += 1 + return counter diff --git a/exercises/practice/collatz-conjecture/.articles/config.json b/exercises/practice/collatz-conjecture/.articles/config.json new file mode 100644 index 0000000000..afb7c6f9e6 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "c6435e29-fd40-42a4-95ba-7c06b9b48f7a", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach for collatz conjecture.", + "authors": ["meatball133", "bethanyg"] + } + ] +} diff --git a/exercises/practice/collatz-conjecture/.articles/performance/code/Benchmark.py b/exercises/practice/collatz-conjecture/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..aea0162e59 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.articles/performance/code/Benchmark.py @@ -0,0 +1,43 @@ +import timeit + +def steps_if(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + counter = 0 + while number != 1: + if number % 2 == 0: + number /= 2 + else: + number = number * 3 + 1 + counter += 1 + return counter + +def steps_recursion(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + if number == 1: + return 0 + number = number / 2 if number % 2 == 0 else number * 3 + 1 + return 1 + steps_recursion(number) + +def steps_ternary(number): + if number <= 0: + raise ValueError("Only positive integers are allowed") + counter = 0 + while number != 1: + number = number / 2 if number % 2 == 0 else number * 3 + 1 + counter += 1 + return counter + + +starttime = timeit.default_timer() +steps_recursion(100000) +print("Steps with recursion :", timeit.default_timer() - starttime) + +starttime = timeit.default_timer() +steps_ternary(100000) +print("Steps with ternary :", timeit.default_timer() - starttime) + +starttime = timeit.default_timer() +steps_if(100000) +print("Steps with if/else :", timeit.default_timer() - starttime) diff --git a/exercises/practice/collatz-conjecture/.articles/performance/content.md b/exercises/practice/collatz-conjecture/.articles/performance/content.md new file mode 100644 index 0000000000..c5c62aacef --- /dev/null +++ b/exercises/practice/collatz-conjecture/.articles/performance/content.md @@ -0,0 +1,32 @@ +# Performance + +In this approach, we'll find out how to most efficiently calculate Collatz Conjecture in Python. + +The [approaches page][approaches] lists three approaches to this exercise: + +1. [Using recursion][approach-recursion] +2. [Using the ternary operator][approach-ternary-operator] +3. [Using the if/else][approach-if-else] + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. +These test where run in windows 11, using Python 3.11.1. + +``` +Steps with recursion : 0.00015059998258948326 +Steps with ternary : 1.8699909560382366e-05 +Steps with if/else : 1.8799910321831703e-05 +``` + +## Conclusion + +The fastest approach is the one using the ternary operator or the if/else statement. +The slowest approach is the one using recursion, that is because Python isn't optimized for recursion. + +[approaches]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches +[approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator +[approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html diff --git a/exercises/practice/collatz-conjecture/.articles/performance/snippet.md b/exercises/practice/collatz-conjecture/.articles/performance/snippet.md new file mode 100644 index 0000000000..d79a676214 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.articles/performance/snippet.md @@ -0,0 +1,5 @@ +``` +Steps with recursion : 0.00015059998258948326 +Steps with ternary : 1.8699909560382366e-05 +Steps with if/else : 1.8799910321831703e-05 +``` From 4771a453e84f0c756a150c7d64bd4be4b1742864 Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 19:09:18 +0100 Subject: [PATCH 321/826] fix-snippet --- .../.approaches/if-else/snippet.txt | 19 ++++++++----------- .../.approaches/ternary-operator/snippet.txt | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/exercises/practice/collatz-conjecture/.approaches/if-else/snippet.txt b/exercises/practice/collatz-conjecture/.approaches/if-else/snippet.txt index eee0d4a3dc..f16ae89210 100644 --- a/exercises/practice/collatz-conjecture/.approaches/if-else/snippet.txt +++ b/exercises/practice/collatz-conjecture/.approaches/if-else/snippet.txt @@ -1,11 +1,8 @@ -def steps(number): - if number <= 0: - raise ValueError("Only positive integers are allowed") - counter = 0 - while number != 1: - if number % 2 == 0: - number /= 2 - else: - number = number * 3 + 1 - counter += 1 - return counter +counter = 0 +while number != 1: + if number % 2 == 0: + number /= 2 + else: + number = number * 3 + 1 + counter += 1 +return counter \ No newline at end of file diff --git a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/snippet.txt b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/snippet.txt index 5e6fcd8c03..cc87d02e23 100644 --- a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/snippet.txt +++ b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/snippet.txt @@ -5,4 +5,4 @@ def steps(number): while number != 1: number = number / 2 if number % 2 == 0 else number * 3 + 1 counter += 1 - return counter + return counter \ No newline at end of file From 4e047cd722b1512722a01fc344dacf103982408f Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 20:08:15 +0100 Subject: [PATCH 322/826] fix --- .../.approaches/if-else/content.md | 6 +++--- .../.approaches/introduction.md | 16 ++++++++-------- .../.approaches/recursion/content.md | 11 ++++++----- .../.approaches/ternary-operator/content.md | 12 +++++++----- .../.articles/performance/content.md | 2 +- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/exercises/practice/collatz-conjecture/.approaches/if-else/content.md b/exercises/practice/collatz-conjecture/.approaches/if-else/content.md index c78fc06a7e..040b6bccfc 100644 --- a/exercises/practice/collatz-conjecture/.approaches/if-else/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/if-else/content.md @@ -16,14 +16,14 @@ def steps(number): This approach starts with checking if the number is less than or equal to zero. If it is, then it raises a [`ValueError`][value-error]. -After that we declare a counter variable and set it to zero. +After that, we declare a counter variable and set it to zero. Then we start a [`while` loop][while-loop] that will run until the number is equal to one. -Meaning the loop wont run if the number is already one. +Meaning the loop won't run if the number is already one. Inside the loop we check if the number is even. If it is, then we divide it by two. If it isn't, then we multiply it by three and add one. -After that we increment the counter by one. +After that, we increment the counter by one. After the loop is done, we return the counter variable. We have to use a `while` loop here because we don't know how many times the loop will run. diff --git a/exercises/practice/collatz-conjecture/.approaches/introduction.md b/exercises/practice/collatz-conjecture/.approaches/introduction.md index 20fbe6f90f..0ff3a741bd 100644 --- a/exercises/practice/collatz-conjecture/.approaches/introduction.md +++ b/exercises/practice/collatz-conjecture/.approaches/introduction.md @@ -2,17 +2,17 @@ There are various approaches to solving the Collatz Conjecture exercise in Python. You can for example use a while loop or a recursive function. -You can use if and else statements or the ternary operator. +You can also solve it by using if and else statements or the ternary operator. ## General guidance The key to this exercise is to check if the number and then do the correct operation. -Under this process you are suppose to count how many steps it takes to get to one. +Under this process you are supposed to count how many steps it takes to get to one. ## Approach: If/Else This is a good way to solve the exercise, it is easy to understand and it is very readable. -The reason to why you might not want to use this approach is because it is longer than the other approaches. +The reason why you might not want to use this approach is because it is longer than the other approaches. ```python def steps(number): @@ -53,10 +53,10 @@ For more information, check the [Ternary operator approach][approach-ternary-ope In this approach we use a recursive function. A recursive function is a function that calls itself. -The reason to why you want to use this approach is because it is more concise than the other approaches. +The reason why you want to use this approach is because it is more concise than the other approaches. Since it doesn't need a counter variable. -The reason to why you might not want to use this approach is that python has a limit on how many times a function can call itself. +The reason why you might not want to use this approach is that python has a limit on how many times a function can call itself. This limit is 1000 times. ```python @@ -74,9 +74,9 @@ For more information, check the [Recursion approach][approach-recursion]. ## Benchmarks To get a better understanding of the performance of the different approaches, we have created benchmarks. -For more information, check the [Performance article]. +For more information, check the [Performance article][performance-article]. -[performance article]: https://exercism.org/tracks/python/exercises/collatz-conjecture/articles/performance [approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else -[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator [approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator +[performance-article]: https://exercism.org/tracks/python/exercises/collatz-conjecture/articles/performance diff --git a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md index b452045ed8..feeb2fc832 100644 --- a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md @@ -12,13 +12,13 @@ def steps(number): This approach uses [concept:python/recursion]() to solve the problem. Recursion is a programming technique where a function calls itself. -Recursion is a powerful technique, but can be more tricky. +It is a powerful technique, but can be more tricky to implement than a while loop. Recursion isn't that common in Python, it is more common in functional programming languages, like: [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure]. This approach starts with checking if the number is less than or equal to zero. If it is, then it raises a [`ValueError`][value-error]. -After that we check if the number is equal to one. +After that, we check if the number is equal to one. If it is, then we return zero. Then we use the same ternary operator as in the [ternary operator][ternary-operator] approach. @@ -27,16 +27,17 @@ The ternary operator checks if the number is even. If it is, then we divide it by two. If it isn't, then we multiply it by three and add one. -After that we return one plus the result of calling the `steps` function with the new number. +After that, we return one plus the result of calling the `steps` function with the new number. This is the recursion part. Doing this exercise with recursion removes the need for a "counter" variable. Since what we do is that if the number is not one we do `1 + steps(number)`. -Then that function can do the same thing. +Then that function can do the same thing again. Meaning we can get a long chain of `1 + steps(number)` until we reach one and add 0. That will translate to something like this: `1 + 1 + 1 + 1 + 0`. -In python we can't have a function call itself more than 1000 times. +In Python, we can't have a function call itself more than 1000 times. +Which mean problems that require a lot of recursion will fail. [clojure]: https://exercism.org/tracks/clojure [elixir]: https://exercism.org/tracks/elixir diff --git a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md index 518455461c..32f0ad8dd1 100644 --- a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md @@ -11,20 +11,22 @@ def steps(number): return counter ``` +This approach starts with checking if the number is less than or equal to zero. If it is, then it raises a [`ValueError`][value-error]. -After that we declare a counter variable and set it to zero. +After that, we declare a counter variable and set it to zero. Then we start a `while` loop that will run until the number is equal to one. -Meaning the loop wont run if the number is already one. +Meaning the loop won't run if the number is already one. -Inside of the loop we have a [ternary operator][ternary-operator]. -The ternary operator is a one-line `if` and `else` statement. +Inside the loop we have a [ternary operator][ternary-operator]. +A ternary operator is a one-line `if` and `else` statement. That means that we can make the code more concise by using it. + We declare that number is equal to the result of the ternary operator. The ternary operator checks if the number is even. If it is, then we divide it by two. If it isn't, then we multiply it by three and add one. -After that we increment the counter by one. +After that, we increment the counter by one. After the loop is done, we return the counter variable. [ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ diff --git a/exercises/practice/collatz-conjecture/.articles/performance/content.md b/exercises/practice/collatz-conjecture/.articles/performance/content.md index c5c62aacef..eccade40cb 100644 --- a/exercises/practice/collatz-conjecture/.articles/performance/content.md +++ b/exercises/practice/collatz-conjecture/.articles/performance/content.md @@ -26,7 +26,7 @@ The slowest approach is the one using recursion, that is because Python isn't op [approaches]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches [approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else -[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator [approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator [benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.articles/performance/code/Benchmark.py [timeit]: https://docs.python.org/3/library/timeit.html From 85cd543f5f44f44a1ed3ea0aba970774eeb7849a Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 22:10:17 +0100 Subject: [PATCH 323/826] fix --- .../collatz-conjecture/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/collatz-conjecture/.articles/performance/content.md b/exercises/practice/collatz-conjecture/.articles/performance/content.md index eccade40cb..b18aaba87a 100644 --- a/exercises/practice/collatz-conjecture/.articles/performance/content.md +++ b/exercises/practice/collatz-conjecture/.articles/performance/content.md @@ -11,7 +11,7 @@ The [approaches page][approaches] lists three approaches to this exercise: ## Benchmarks To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. -These test where run in windows 11, using Python 3.11.1. +These test were run in windows 11, using Python 3.11.1. ``` Steps with recursion : 0.00015059998258948326 From 9b7c79897d1fbb76f69bb7af8dd0a746b726a0dc Mon Sep 17 00:00:00 2001 From: Carl Date: Sat, 31 Dec 2022 15:54:08 +0100 Subject: [PATCH 324/826] Re-run the test on python 3.11.1 instead of python 3.10 --- .../collatz-conjecture/.articles/performance/content.md | 8 ++++---- .../collatz-conjecture/.articles/performance/snippet.md | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/exercises/practice/collatz-conjecture/.articles/performance/content.md b/exercises/practice/collatz-conjecture/.articles/performance/content.md index b18aaba87a..7e68380391 100644 --- a/exercises/practice/collatz-conjecture/.articles/performance/content.md +++ b/exercises/practice/collatz-conjecture/.articles/performance/content.md @@ -14,15 +14,15 @@ To benchmark the approaches, we wrote a [small benchmark application][benchmark- These test were run in windows 11, using Python 3.11.1. ``` -Steps with recursion : 0.00015059998258948326 -Steps with ternary : 1.8699909560382366e-05 -Steps with if/else : 1.8799910321831703e-05 +Steps with recursion : 4.1499966755509377e-05 +Steps with ternary : 2.1900050342082977e-05 +Steps with if/else : 2.0900042727589607e-05 ``` ## Conclusion The fastest approach is the one using the ternary operator or the if/else statement. -The slowest approach is the one using recursion, that is because Python isn't optimized for recursion. +The slowest approach is the one using recursion, that is because Python isn't as optimized for recursion. [approaches]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches [approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else diff --git a/exercises/practice/collatz-conjecture/.articles/performance/snippet.md b/exercises/practice/collatz-conjecture/.articles/performance/snippet.md index d79a676214..b95418a512 100644 --- a/exercises/practice/collatz-conjecture/.articles/performance/snippet.md +++ b/exercises/practice/collatz-conjecture/.articles/performance/snippet.md @@ -1,5 +1,5 @@ ``` -Steps with recursion : 0.00015059998258948326 -Steps with ternary : 1.8699909560382366e-05 -Steps with if/else : 1.8799910321831703e-05 +Steps with recursion : 4.1499966755509377e-05 +Steps with ternary : 2.1900050342082977e-05 +Steps with if/else : 2.0900042727589607e-05 ``` From 707bba1658f878daeb48d12bf84fbcf72a090670 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 10 Jan 2023 22:11:53 +0100 Subject: [PATCH 325/826] Apply suggestions from code review Co-authored-by: BethanyG --- .../.approaches/if-else/content.md | 4 +-- .../.approaches/introduction.md | 12 ++++---- .../.approaches/recursion/content.md | 30 +++++++++++-------- .../.approaches/ternary-operator/content.md | 20 ++++++------- .../.articles/performance/content.md | 8 ++--- 5 files changed, 38 insertions(+), 36 deletions(-) diff --git a/exercises/practice/collatz-conjecture/.approaches/if-else/content.md b/exercises/practice/collatz-conjecture/.approaches/if-else/content.md index 040b6bccfc..56cc8e4257 100644 --- a/exercises/practice/collatz-conjecture/.approaches/if-else/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/if-else/content.md @@ -24,9 +24,9 @@ Inside the loop we check if the number is even. If it is, then we divide it by two. If it isn't, then we multiply it by three and add one. After that, we increment the counter by one. -After the loop is done, we return the counter variable. +After the loop completes, we return the counter variable. -We have to use a `while` loop here because we don't know how many times the loop will run. +We use a `while` loop here because we don't know exactly how many times the loop will run. [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError [while-loop]: https://realpython.com/python-while-loop/ diff --git a/exercises/practice/collatz-conjecture/.approaches/introduction.md b/exercises/practice/collatz-conjecture/.approaches/introduction.md index 0ff3a741bd..4e9c155c35 100644 --- a/exercises/practice/collatz-conjecture/.approaches/introduction.md +++ b/exercises/practice/collatz-conjecture/.approaches/introduction.md @@ -32,9 +32,8 @@ For more information, check the [if/else approach][approach-if-else]. ## Approach: Ternary operator -In this approach we use the ternary operator. -It allows us to write a one-line if and else statement. -This makes the code more concise. +In this approach we replace the `if/else` multi-line construction with a [conditional expression][conditional-expression], sometimes called a _ternary operator_. +This syntax allows us to write a one-line `if/ else` check, making the code more concise. ```python def steps(number): @@ -53,11 +52,9 @@ For more information, check the [Ternary operator approach][approach-ternary-ope In this approach we use a recursive function. A recursive function is a function that calls itself. -The reason why you want to use this approach is because it is more concise than the other approaches. -Since it doesn't need a counter variable. +This approach can be more concise than other approaches, and may also be more readable for some audiences. -The reason why you might not want to use this approach is that python has a limit on how many times a function can call itself. -This limit is 1000 times. +The reason why you might not want to use this approach is that Python has a [recursion limit][recursion-limit] with a default of 1000. ```python def steps(number): @@ -79,4 +76,5 @@ For more information, check the [Performance article][performance-article]. [approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else [approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion [approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator +[conditional-expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions [performance-article]: https://exercism.org/tracks/python/exercises/collatz-conjecture/articles/performance diff --git a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md index feeb2fc832..cdfa25335c 100644 --- a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md @@ -21,27 +21,31 @@ If it is, then it raises a [`ValueError`][value-error]. After that, we check if the number is equal to one. If it is, then we return zero. -Then we use the same ternary operator as in the [ternary operator][ternary-operator] approach. -We declare that number is equal to the result of the ternary operator. -The ternary operator checks if the number is even. -If it is, then we divide it by two. -If it isn't, then we multiply it by three and add one. +We then use the same conditional expression/ternary operator as the [ternary operator][ternary-operator] approach does. +We assign **number** to the result of the conditional expression. +The expression checks if the number is even. +If the number is even, we divide it by two. +If it isn't, we multiply it by three and add one. -After that, we return one plus the result of calling the `steps` function with the new number. +After that, we `return` one plus the result of calling the `steps` function with the new number value. This is the recursion part. -Doing this exercise with recursion removes the need for a "counter" variable. -Since what we do is that if the number is not one we do `1 + steps(number)`. -Then that function can do the same thing again. -Meaning we can get a long chain of `1 + steps(number)` until we reach one and add 0. -That will translate to something like this: `1 + 1 + 1 + 1 + 0`. +Solving this exercise with recursion removes the need for a "counter" variable and the instantiation of a `loop`. +If the number is not equal to one, we call `1 + steps(number)`. +Then the `steps` function can execute the same code again with new values. +Meaning we can get a long chain or stack of `1 + steps(number)` until the number reaches one and the code adds 0. +That translates to something like this: `1 + 1 + 1 + 1 + 0`. -In Python, we can't have a function call itself more than 1000 times. -Which mean problems that require a lot of recursion will fail. +In Python, we can't have a function call itself more than 1000 times by default. +Code that exceeds this recursion limit will throw a [RecursionError][recursion-error]. +While it is possible to adjust the [recursion limit][recursion-limit], doing so risks crashing Python and may also crash your system. +Casually raising the recursion limit is not recommended. [clojure]: https://exercism.org/tracks/clojure [elixir]: https://exercism.org/tracks/elixir [haskell]: https://exercism.org/tracks/haskell [recursion]: https://realpython.com/python-thinking-recursively/ +[recursion-error]: https://docs.python.org/3/library/exceptions.html#RecursionError +[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit [ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError diff --git a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md index 32f0ad8dd1..7a1368cf6e 100644 --- a/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/ternary-operator/content.md @@ -12,22 +12,22 @@ def steps(number): ``` This approach starts with checking if the number is less than or equal to zero. -If it is, then it raises a [`ValueError`][value-error]. +If it is, then a [`ValueError`][value-error] is raised. -After that, we declare a counter variable and set it to zero. +After that, a counter variable is assigned to zero. Then we start a `while` loop that will run until the number is equal to one. Meaning the loop won't run if the number is already one. -Inside the loop we have a [ternary operator][ternary-operator]. -A ternary operator is a one-line `if` and `else` statement. -That means that we can make the code more concise by using it. +Inside the loop we have a [ternary operator][ternary-operator] or [conditional expression][conditional-expression]. +A ternary operator/conditional expression can be viewed as a one-line `if/else` statement. +Using a one-line construct can make the code more concise. -We declare that number is equal to the result of the ternary operator. -The ternary operator checks if the number is even. +We assign the number value to the result of the ternary operator. +The ternary operator/conditional expression checks if the number is even. If it is, then we divide it by two. -If it isn't, then we multiply it by three and add one. -After that, we increment the counter by one. -After the loop is done, we return the counter variable. +If the number is not even, we multiply by three and add one. +Then the counter is incremented by one. +When the loop completes, we return the counter value. [ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError diff --git a/exercises/practice/collatz-conjecture/.articles/performance/content.md b/exercises/practice/collatz-conjecture/.articles/performance/content.md index 7e68380391..5b1c3d43fd 100644 --- a/exercises/practice/collatz-conjecture/.articles/performance/content.md +++ b/exercises/practice/collatz-conjecture/.articles/performance/content.md @@ -1,6 +1,6 @@ # Performance -In this approach, we'll find out how to most efficiently calculate Collatz Conjecture in Python. +In this approach, we'll find out how to most efficiently calculate the Collatz Conjecture in Python. The [approaches page][approaches] lists three approaches to this exercise: @@ -11,7 +11,7 @@ The [approaches page][approaches] lists three approaches to this exercise: ## Benchmarks To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. -These test were run in windows 11, using Python 3.11.1. +These tests were run in windows 11, using Python 3.11.1. ``` Steps with recursion : 4.1499966755509377e-05 @@ -21,8 +21,8 @@ Steps with if/else : 2.0900042727589607e-05 ## Conclusion -The fastest approach is the one using the ternary operator or the if/else statement. -The slowest approach is the one using recursion, that is because Python isn't as optimized for recursion. +The fastest approach is the one using the `if/else` statement, followed by the one using the ternary operator/conditional expression. +The slowest approach is the one using recursion, probably because Python isn't as optimized for recursion as it is for iteration. [approaches]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches [approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else From 957c260db2da549ec4af1191bf366f3b59a92edf Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 10 Jan 2023 22:16:49 +0100 Subject: [PATCH 326/826] Update exercises/practice/collatz-conjecture/.approaches/introduction.md Co-authored-by: BethanyG --- .../practice/collatz-conjecture/.approaches/introduction.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/collatz-conjecture/.approaches/introduction.md b/exercises/practice/collatz-conjecture/.approaches/introduction.md index 4e9c155c35..6a6d487f26 100644 --- a/exercises/practice/collatz-conjecture/.approaches/introduction.md +++ b/exercises/practice/collatz-conjecture/.approaches/introduction.md @@ -75,6 +75,7 @@ For more information, check the [Performance article][performance-article]. [approach-if-else]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/if-else [approach-recursion]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/recursion +[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit [approach-ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator [conditional-expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions [performance-article]: https://exercism.org/tracks/python/exercises/collatz-conjecture/articles/performance From 04edbac62469cf1ed3d4ab49bce356b522328d37 Mon Sep 17 00:00:00 2001 From: Carl Date: Tue, 10 Jan 2023 23:39:35 +0100 Subject: [PATCH 327/826] Fix uuid --- .../practice/collatz-conjecture/.approaches/config.json | 6 +++--- exercises/practice/collatz-conjecture/.articles/config.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/collatz-conjecture/.approaches/config.json b/exercises/practice/collatz-conjecture/.approaches/config.json index 2ba3bf4c5b..5730c94d97 100644 --- a/exercises/practice/collatz-conjecture/.approaches/config.json +++ b/exercises/practice/collatz-conjecture/.approaches/config.json @@ -5,21 +5,21 @@ }, "approaches": [ { - "uuid": "7b3aeaef-11ef-427c-9086-d2c6d4c022f1", + "uuid": "d92adc98-36fd-49bb-baf5-e4588387841c", "slug": "if-else", "title": "If/Else", "blurb": "Use if and else", "authors": ["bethanyg", "meatball133"] }, { - "uuid": "a2d8742b-9516-44c8-832d-111874430de0", + "uuid": "d7703aef-1510-4ec8-b6ce-ca608b5b8f70", "slug": "ternary-operator", "title": "Ternary operator", "blurb": "Use a ternary operator", "authors": ["bethanyg", "meatball133"] }, { - "uuid": "01da60d9-c133-41de-90fd-afbba7df2980", + "uuid": "b1220645-124a-4994-96c4-3b2b710fd562", "slug": "recursion", "title": "Recursion", "blurb": "Use recursion", diff --git a/exercises/practice/collatz-conjecture/.articles/config.json b/exercises/practice/collatz-conjecture/.articles/config.json index afb7c6f9e6..1bab002169 100644 --- a/exercises/practice/collatz-conjecture/.articles/config.json +++ b/exercises/practice/collatz-conjecture/.articles/config.json @@ -1,7 +1,7 @@ { "articles": [ { - "uuid": "c6435e29-fd40-42a4-95ba-7c06b9b48f7a", + "uuid": "c37be489b-791a-463a-94c0-564ca0277da2", "slug": "performance", "title": "Performance deep dive", "blurb": "Deep dive to find out the most performant approach for collatz conjecture.", From 759ec3f804a409abbafa5c0882d4fa4c25e5e45b Mon Sep 17 00:00:00 2001 From: Carl Date: Tue, 10 Jan 2023 23:43:16 +0100 Subject: [PATCH 328/826] fix uuid --- exercises/practice/collatz-conjecture/.articles/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/collatz-conjecture/.articles/config.json b/exercises/practice/collatz-conjecture/.articles/config.json index 1bab002169..a9341a5153 100644 --- a/exercises/practice/collatz-conjecture/.articles/config.json +++ b/exercises/practice/collatz-conjecture/.articles/config.json @@ -1,7 +1,7 @@ { "articles": [ { - "uuid": "c37be489b-791a-463a-94c0-564ca0277da2", + "uuid": "f5071a22-f13a-4650-afea-b7aaee8f2b12", "slug": "performance", "title": "Performance deep dive", "blurb": "Deep dive to find out the most performant approach for collatz conjecture.", From d92309043536511b4fce63971f6aee2cc7fb8167 Mon Sep 17 00:00:00 2001 From: Carl Date: Tue, 10 Jan 2023 00:40:42 +0100 Subject: [PATCH 329/826] Started --- .../.approaches/Dictionary/content.md | 51 ++++++++ .../.approaches/Dictionary/snippet.txt | 2 + .../.approaches/Enum/content.md | 44 +++++++ .../.approaches/Enum/snippet.txt | 8 ++ .../scrabble-score/.approaches/config.json | 36 ++++++ .../.approaches/introduction.md | 115 ++++++++++++++++++ .../.approaches/nested-tuple/content.md | 32 +++++ .../.approaches/nested-tuple/snippet.txt | 8 ++ .../.approaches/two-sequences/content.md | 25 ++++ .../.approaches/two-sequences/snippet.txt | 5 + 10 files changed, 326 insertions(+) create mode 100644 exercises/practice/scrabble-score/.approaches/Dictionary/content.md create mode 100644 exercises/practice/scrabble-score/.approaches/Dictionary/snippet.txt create mode 100644 exercises/practice/scrabble-score/.approaches/Enum/content.md create mode 100644 exercises/practice/scrabble-score/.approaches/Enum/snippet.txt create mode 100644 exercises/practice/scrabble-score/.approaches/config.json create mode 100644 exercises/practice/scrabble-score/.approaches/introduction.md create mode 100644 exercises/practice/scrabble-score/.approaches/nested-tuple/content.md create mode 100644 exercises/practice/scrabble-score/.approaches/nested-tuple/snippet.txt create mode 100644 exercises/practice/scrabble-score/.approaches/two-sequences/content.md create mode 100644 exercises/practice/scrabble-score/.approaches/two-sequences/snippet.txt diff --git a/exercises/practice/scrabble-score/.approaches/Dictionary/content.md b/exercises/practice/scrabble-score/.approaches/Dictionary/content.md new file mode 100644 index 0000000000..55e7b1357f --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/Dictionary/content.md @@ -0,0 +1,51 @@ +# Dictionary + +```python +LETTER_SCORES = { + 'A': 1, + 'E': 1, + 'I': 1, + 'O': 1, + 'U': 1, + 'L': 1, + 'N': 1, + 'R': 1, + 'S': 1, + 'T': 1, + 'D': 2, + 'G': 2, + 'B': 3, + 'C': 3, + 'M': 3, + 'P': 3, + 'F': 4, + 'H': 4, + 'V': 4, + 'W': 4, + 'Y': 4, + 'K': 5, + 'J': 8, + 'X': 8, + 'Q': 10, + 'Z': 10 +} + +def score(word): + return sum(LETTER_SCORES[letter.upper()] for letter in word) +``` + +The code starts with initializing a constant that is a [dictionary][dictionary] that holds all the letters as different key and their respective score as a value. +Then it defines a function that takes a word as an argument. + +The function returns the built in function [`sum`][sum] that takes a [generator expression][generator-expersion] that iterates over the letters in the word. +What a generator expression does is that it generates the values on the fly. +Meaning that it doesn't have to use a lot of memory since it uses the last value and generates the next value. + +Under the generation a letter is given from the string, then it is converted to upcase, and then being looked up at inside of the dictionary and the value is returned. + +There is also a very similar approach that uses a dictionary transposition. +Although that approach requires more computational calculation therefore is this approach more efficient. + +[dictionary]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[generator-expersion]: https://peps.python.org/pep-0289/ +[sum]: https://docs.python.org/3/library/functions.html#sum diff --git a/exercises/practice/scrabble-score/.approaches/Dictionary/snippet.txt b/exercises/practice/scrabble-score/.approaches/Dictionary/snippet.txt new file mode 100644 index 0000000000..c0b4b3f970 --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/Dictionary/snippet.txt @@ -0,0 +1,2 @@ +def score(word): + return sum(LETTER_SCORES[letter.upper()] for letter in word) \ No newline at end of file diff --git a/exercises/practice/scrabble-score/.approaches/Enum/content.md b/exercises/practice/scrabble-score/.approaches/Enum/content.md new file mode 100644 index 0000000000..a9fd97b97c --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/Enum/content.md @@ -0,0 +1,44 @@ +# Enum + +```python +from enum import IntEnum + +class Scrabble(IntEnum): + A = E = I = O = U = L = N = R = S = T = 1 + D = G = 2 + B = C = M = P = 3 + F = H = V = W = Y = 4 + K = 5 + J = X = 8 + Q = Z = 10 + +def score(word): + return sum(Scrabble[char.upper()] for char in word) +``` + +This approach uses an [`enum`][enum] to define the score of each letter. +An `enum` or known as a enumerations is sets of named constant and is immutable. +`enum` was added to python standard library also known as stdlib in python 3.4. + +This approach uses an [`intEnum`][int-enum] it works very similar to a normal `enum` but it has the added benefit that the values are integers. +Thereby acts like integers. + +To use an `intEnum` you need to [import][import] it using: `from enum import IntEnum`. +Then you can define the `enum` class. + +The `enum` class is defined by using the [`class`][classes] keyword. +Then you need to specify the name of the class. + +After that is constant declared by giving the constant capital letters and the value is assigned by using the `=` operator. +This approach works by giving all the letters as constants and then value of the constant is the score of the letter. +After the `enum` is defined, the `score` function is defined. + +The `score` function takes a word as a parameter. +And uses the same [generator expression][generator-expersion] as the [dictionary approach][dictionary-approach]. +But instead of looking up the value in a dictionary it looks it up in the `enum` class. + +[classes]: https://docs.python.org/3/tutorial/classes.html +[enum]: https://docs.python.org/3/library/enum.html +[generator-expersion]: https://peps.python.org/pep-0289/ +[int-enum]: https://docs.python.org/3/library/enum.html#enum.IntEnum +[import]: https://docs.python.org/3/reference/import.html diff --git a/exercises/practice/scrabble-score/.approaches/Enum/snippet.txt b/exercises/practice/scrabble-score/.approaches/Enum/snippet.txt new file mode 100644 index 0000000000..155b62b9c3 --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/Enum/snippet.txt @@ -0,0 +1,8 @@ +class Scrabble(IntEnum): + A = E = I = O = U = L = N = R = S = T = 1 + D = G = 2 + B = C = M = P = 3 + F = H = V = W = Y = 4 + K = 5 + J = X = 8 + Q = Z = 10 \ No newline at end of file diff --git a/exercises/practice/scrabble-score/.approaches/config.json b/exercises/practice/scrabble-score/.approaches/config.json new file mode 100644 index 0000000000..0fef67cf03 --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/config.json @@ -0,0 +1,36 @@ +{ + "introduction": { + "authors": ["meatball133", "bethanyg"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "62f556c0-27a6-43e5-80ea-d7abd921f0e7", + "slug": "enum", + "title": "Enum", + "blurb": "Define a enum to solve the problem", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "21c0fe14-585f-4e14-9f31-1d294a8a622c", + "slug": "dictionary", + "title": "Dictionary", + "blurb": "Dictionary approach", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "34911050-b424-4289-9d3f-aa5f0e8f1630", + "slug": "nested-tuple", + "title": "Nested Tuple", + "blurb": "Nested Tuple approach", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "e9b8e10b-23c7-4c2a-afbe-3c3499468919", + "slug": "two-sequences", + "title": "Two Sequences", + "blurb": "Two Sequences approach", + "authors": ["meatball133", "bethanyg"] + } + ] +} diff --git a/exercises/practice/scrabble-score/.approaches/introduction.md b/exercises/practice/scrabble-score/.approaches/introduction.md new file mode 100644 index 0000000000..4a29440827 --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/introduction.md @@ -0,0 +1,115 @@ +# Introduction + +There are various ways to solve `scrabble-score`. +This approaches document shows different strategies to solve this exercise + +## General guidance + +The goal of this exercise is to write a function that calculates the scrabble score for a given word. +The problem is that + +## Approach: Using a single dictionary + +Using a single dictionary is an approach, it is simple and fast. +It is also very pythonic. + +```python +LETTER_SCORES = { + 'A': 1, + 'E': 1, + 'I': 1, + 'O': 1, + 'U': 1, + 'L': 1, + 'N': 1, + 'R': 1, + 'S': 1, + 'T': 1, + 'D': 2, + 'G': 2, + 'B': 3, + 'C': 3, + 'M': 3, + 'P': 3, + 'F': 4, + 'H': 4, + 'V': 4, + 'W': 4, + 'Y': 4, + 'K': 5, + 'J': 8, + 'X': 8, + 'Q': 10, + 'Z': 10 +} + +def score(word): + return sum(LETTER_SCORES[letter.upper()] for letter in word) +``` + +For more information, check the [Dictionary Approach][dictionary-approach]. + +## Approach: Using two sequences + +Using two sequences is an approach, it is fast. +Although the reason you might not want to do this is that it is hard to read. + +```python +KEYS = "AEIOULNRSTDGBCMPFHVWYKJXQZ" +SCORES = [1] * 10 + [2] * 2 + [3] * 4 + [4] * 5 + [5] * 1 + [8] * 2 +[10] * 2 + +def score(word): + return sum(SCORES[KEYS.index(letter.upper())] for letter in word) +``` + +For more information, check the [Two Sequences Approach][two-sequences-approach]. + +## Approach: Enum + +Using an `enum` is an approach, it is short and easy to read. +Although it is more complicated since it uses a class. + +```python +from enum import IntEnum + +class Scrabble(IntEnum): + A = E = I = O = U = L = N = R = S = T = 1 + D = G = 2 + B = C = M = P = 3 + F = H = V = W = Y = 4 + K = 5 + J = X = 8 + Q = Z = 10 + +def score(word): + return sum(Scrabble[char.upper()] for char in word) +``` + +You can read more about how to achieve this optimization in: [Enum Approach][enum-approach]. + +## Approach: Using a nested tuple + +Tuples in python is more memory efficent than using a dictonary in python. +Although this solution since it is iterating over a tuple for every letter so is it slower. + +```python +LETTERS_OF_SCORE = ( + ("AEIOULNRST", 1), + ("DG", 2), + ("BCMP", 3), + ("FHVWY", 4), + ("K", 5), + ("JX", 8), + ("QZ", 10), +) + +def score(word): + return sum(for character in word for letters, score in LETTERS_OF_SCORE if character in letters) +``` + +For more information, check the [Nested Tuple Approach][nested-tuple-approach]. + +[dictionary-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/dictionary +[enum-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/enum +[nested-tuple-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/nested-tuple +[two-sequences-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/two-sequences diff --git a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md new file mode 100644 index 0000000000..ff92681738 --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md @@ -0,0 +1,32 @@ +# Nested Tuple + +```python +LETTERS_OF_SCORE = ( + ("AEIOULNRST", 1), + ("DG", 2), + ("BCMP", 3), + ("FHVWY", 4), + ("K", 5), + ("JX", 8), + ("QZ", 10), +) + +def score(word): + return sum(score for character in word for letters, score in LETTERS_OF_SCORE if character.upper() in letters) +``` + +The code starts with initializing a constant with a [tuple][tuple] of tuples. +Inside of the inner tuples there is 2 values, the first value is a string of letters and the second value is the score of the letters. + +Then it defines a function that takes a word as an argument. +The function returns a [generator expression][generator-expersion] similar to the [dictionary approach][dictionary-approach]. +The difference is that it uses a nested [for loop][for-loop] to iterate over the letters and the tuples. +There we iterate over the characters in the word and then iterate over the tuples. +There the tuple is unpacked into the letters and the score. +You can read more about unpacking in the [concept:python/unpacking-and-multiple-assignment](). + +Then we check if the character is in the letters and if it is we return the score. + +[tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences +[generator-expersion]: https://peps.python.org/pep-0289/ +[for-loop]: https://realpython.com/python-for-loop/ diff --git a/exercises/practice/scrabble-score/.approaches/nested-tuple/snippet.txt b/exercises/practice/scrabble-score/.approaches/nested-tuple/snippet.txt new file mode 100644 index 0000000000..d2b19ebbbf --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/nested-tuple/snippet.txt @@ -0,0 +1,8 @@ +LETTERS_OF_SCORE = ( + ("AEIOULNRST", 1), + ("DG", 2), + ("BCMP", 3), + ("FHVWY", 4), + ("K", 5), + ("JX", 8), + ("QZ", 10),) \ No newline at end of file diff --git a/exercises/practice/scrabble-score/.approaches/two-sequences/content.md b/exercises/practice/scrabble-score/.approaches/two-sequences/content.md new file mode 100644 index 0000000000..f677bb4b43 --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/two-sequences/content.md @@ -0,0 +1,25 @@ +# Two sequences + +```python +KEYS = "AEIOULNRSTDGBCMPFHVWYKJXQZ" +SCORES = [1] * 10 + [2] * 2 + [3] * 4 + [4] * 5 + [5] * 1 + [8] * 2 + [10] * 2 + +def score(word): + return sum(SCORES[KEYS.index(letter.upper())] for letter in word) +``` + +This approach uses a string and a [list][list], both of these data types belongs to the data type [sequences][sequence]. +It has a constant with a string with letters and then it has a constant of a list with corresponding score for the same index as the string. + +The `score` function takes a word as a parameter. +And uses the same [generator expression][generator-expersion] as the [dictionary approach][dictionary-approach]. + +The difference is that instead of using a [dictionary][dictionary] and looked up the score inside. +This approach gets the index of the letter in the KEYS constant and then then looks up the value for that index in SCORES list. +Then takes that value and return that to the generator expression. + +[dictionary]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[dictionary-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/dictionary +[list]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists +[sequence]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[generator-expersion]: https://peps.python.org/pep-0289/ diff --git a/exercises/practice/scrabble-score/.approaches/two-sequences/snippet.txt b/exercises/practice/scrabble-score/.approaches/two-sequences/snippet.txt new file mode 100644 index 0000000000..fd37416b39 --- /dev/null +++ b/exercises/practice/scrabble-score/.approaches/two-sequences/snippet.txt @@ -0,0 +1,5 @@ +KEYS = "AEIOULNRSTDGBCMPFHVWYKJXQZ" +SCORES = [1] * 10 + [2] * 2 + [3] * 4 + [4] * 5 + [5] * 1 + [8] * 2 +[10] * 2 + +def score(word): + return sum(SCORES[KEYS.index(letter.upper())] for letter in word) From 85865aa8929a50c52b8fbecd7097ccd0aae20c6f Mon Sep 17 00:00:00 2001 From: Carl Date: Tue, 10 Jan 2023 00:44:01 +0100 Subject: [PATCH 330/826] Fix dictonary name --- .../.approaches/{Dictionary => dictionary}/content.md | 0 .../.approaches/{Dictionary => dictionary}/snippet.txt | 0 .../practice/scrabble-score/.approaches/{Enum => enum}/content.md | 0 .../scrabble-score/.approaches/{Enum => enum}/snippet.txt | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename exercises/practice/scrabble-score/.approaches/{Dictionary => dictionary}/content.md (100%) rename exercises/practice/scrabble-score/.approaches/{Dictionary => dictionary}/snippet.txt (100%) rename exercises/practice/scrabble-score/.approaches/{Enum => enum}/content.md (100%) rename exercises/practice/scrabble-score/.approaches/{Enum => enum}/snippet.txt (100%) diff --git a/exercises/practice/scrabble-score/.approaches/Dictionary/content.md b/exercises/practice/scrabble-score/.approaches/dictionary/content.md similarity index 100% rename from exercises/practice/scrabble-score/.approaches/Dictionary/content.md rename to exercises/practice/scrabble-score/.approaches/dictionary/content.md diff --git a/exercises/practice/scrabble-score/.approaches/Dictionary/snippet.txt b/exercises/practice/scrabble-score/.approaches/dictionary/snippet.txt similarity index 100% rename from exercises/practice/scrabble-score/.approaches/Dictionary/snippet.txt rename to exercises/practice/scrabble-score/.approaches/dictionary/snippet.txt diff --git a/exercises/practice/scrabble-score/.approaches/Enum/content.md b/exercises/practice/scrabble-score/.approaches/enum/content.md similarity index 100% rename from exercises/practice/scrabble-score/.approaches/Enum/content.md rename to exercises/practice/scrabble-score/.approaches/enum/content.md diff --git a/exercises/practice/scrabble-score/.approaches/Enum/snippet.txt b/exercises/practice/scrabble-score/.approaches/enum/snippet.txt similarity index 100% rename from exercises/practice/scrabble-score/.approaches/Enum/snippet.txt rename to exercises/practice/scrabble-score/.approaches/enum/snippet.txt From 12e692f032154f13fc610549dd7396807b41b42e Mon Sep 17 00:00:00 2001 From: meatball Date: Tue, 10 Jan 2023 10:10:16 +0100 Subject: [PATCH 331/826] Fixes --- .../.approaches/dictionary/content.md | 4 ++-- .../scrabble-score/.approaches/enum/content.md | 12 ++++++------ .../scrabble-score/.approaches/introduction.md | 12 ++++++------ .../.approaches/nested-tuple/content.md | 14 ++++++++------ .../.approaches/two-sequences/content.md | 9 +++++---- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/exercises/practice/scrabble-score/.approaches/dictionary/content.md b/exercises/practice/scrabble-score/.approaches/dictionary/content.md index 55e7b1357f..b046481804 100644 --- a/exercises/practice/scrabble-score/.approaches/dictionary/content.md +++ b/exercises/practice/scrabble-score/.approaches/dictionary/content.md @@ -34,8 +34,8 @@ def score(word): return sum(LETTER_SCORES[letter.upper()] for letter in word) ``` -The code starts with initializing a constant that is a [dictionary][dictionary] that holds all the letters as different key and their respective score as a value. -Then it defines a function that takes a word as an argument. +The code starts with initializing a constant that is a [dictionary][dictionary] there each letter is a key and their respective score as a value. +Then a function is defined that takes a word as an argument. The function returns the built in function [`sum`][sum] that takes a [generator expression][generator-expersion] that iterates over the letters in the word. What a generator expression does is that it generates the values on the fly. diff --git a/exercises/practice/scrabble-score/.approaches/enum/content.md b/exercises/practice/scrabble-score/.approaches/enum/content.md index a9fd97b97c..cad5b6bdec 100644 --- a/exercises/practice/scrabble-score/.approaches/enum/content.md +++ b/exercises/practice/scrabble-score/.approaches/enum/content.md @@ -13,12 +13,12 @@ class Scrabble(IntEnum): Q = Z = 10 def score(word): - return sum(Scrabble[char.upper()] for char in word) + return sum(Scrabble[character.upper()] for character in word) ``` This approach uses an [`enum`][enum] to define the score of each letter. -An `enum` or known as a enumerations is sets of named constant and is immutable. -`enum` was added to python standard library also known as stdlib in python 3.4. +An `enum` or known as an **enumeration** is sets of named constant and is immutable. +`enum` was added to python standard library (_also known as stdlib_) in python 3.4. This approach uses an [`intEnum`][int-enum] it works very similar to a normal `enum` but it has the added benefit that the values are integers. Thereby acts like integers. @@ -29,13 +29,13 @@ Then you can define the `enum` class. The `enum` class is defined by using the [`class`][classes] keyword. Then you need to specify the name of the class. -After that is constant declared by giving the constant capital letters and the value is assigned by using the `=` operator. +After that is the constant in the enum declared by giving the constant capital letters and the value is assigned by using the `=` operator. This approach works by giving all the letters as constants and then value of the constant is the score of the letter. After the `enum` is defined, the `score` function is defined. The `score` function takes a word as a parameter. -And uses the same [generator expression][generator-expersion] as the [dictionary approach][dictionary-approach]. -But instead of looking up the value in a dictionary it looks it up in the `enum` class. +And uses the same [generator expression][generator-expersion] as the [dictionary approach][dictionary-approach] but with a slight modification. +Which is that instead of looking up the value in a dictionary it looks it up in the `enum` class. [classes]: https://docs.python.org/3/tutorial/classes.html [enum]: https://docs.python.org/3/library/enum.html diff --git a/exercises/practice/scrabble-score/.approaches/introduction.md b/exercises/practice/scrabble-score/.approaches/introduction.md index 4a29440827..2891e55a6d 100644 --- a/exercises/practice/scrabble-score/.approaches/introduction.md +++ b/exercises/practice/scrabble-score/.approaches/introduction.md @@ -1,12 +1,12 @@ # Introduction There are various ways to solve `scrabble-score`. -This approaches document shows different strategies to solve this exercise +This approache document shows different strategies to solve this exercise. ## General guidance The goal of this exercise is to write a function that calculates the scrabble score for a given word. -The problem is that +The problem is that the scrabble score is calculated by the sum of the scores of each letter in the word. ## Approach: Using a single dictionary @@ -51,7 +51,7 @@ For more information, check the [Dictionary Approach][dictionary-approach]. ## Approach: Using two sequences -Using two sequences is an approach, it is fast. +Using two sequences is an approach, it removes the need of using a nested data structure or a dictonary. Although the reason you might not want to do this is that it is hard to read. ```python @@ -67,7 +67,7 @@ For more information, check the [Two Sequences Approach][two-sequences-approach] ## Approach: Enum Using an `enum` is an approach, it is short and easy to read. -Although it is more complicated since it uses a class. +Although it is more complicated since it uses a oop (object oriented programmering) elements. ```python from enum import IntEnum @@ -82,10 +82,10 @@ class Scrabble(IntEnum): Q = Z = 10 def score(word): - return sum(Scrabble[char.upper()] for char in word) + return sum(Scrabble[letter.upper()] for letter in word) ``` -You can read more about how to achieve this optimization in: [Enum Approach][enum-approach]. +For more information, check the [Enum Approach][enum-approach]. ## Approach: Using a nested tuple diff --git a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md index ff92681738..4498d1841f 100644 --- a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md +++ b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md @@ -15,13 +15,15 @@ def score(word): return sum(score for character in word for letters, score in LETTERS_OF_SCORE if character.upper() in letters) ``` -The code starts with initializing a constant with a [tuple][tuple] of tuples. -Inside of the inner tuples there is 2 values, the first value is a string of letters and the second value is the score of the letters. +The code starts with initializing a constant with a [tuple][tuple] of tuples (_also known as a nested tuple_). +Inside of the inner tuples there is 2 values, the first value is a string of letters and the second value is the score for the letters. -Then it defines a function that takes a word as an argument. -The function returns a [generator expression][generator-expersion] similar to the [dictionary approach][dictionary-approach]. -The difference is that it uses a nested [for loop][for-loop] to iterate over the letters and the tuples. -There we iterate over the characters in the word and then iterate over the tuples. +Then a function is defined that takes a word as an argument. +The function returns a [generator expression][generator-expersion] similar to the [dictionary approach][dictionary-approach] but has some slight modifcations. + +The difference is that this one uses a nested [for loop][for-loop] to iterate over the letters and the tuples. +We first iterate over the characters in the word and then iterate over the tuples. +Which means that for each letter are we iterating over all of the tuples. There the tuple is unpacked into the letters and the score. You can read more about unpacking in the [concept:python/unpacking-and-multiple-assignment](). diff --git a/exercises/practice/scrabble-score/.approaches/two-sequences/content.md b/exercises/practice/scrabble-score/.approaches/two-sequences/content.md index f677bb4b43..944da04298 100644 --- a/exercises/practice/scrabble-score/.approaches/two-sequences/content.md +++ b/exercises/practice/scrabble-score/.approaches/two-sequences/content.md @@ -8,13 +8,14 @@ def score(word): return sum(SCORES[KEYS.index(letter.upper())] for letter in word) ``` -This approach uses a string and a [list][list], both of these data types belongs to the data type [sequences][sequence]. -It has a constant with a string with letters and then it has a constant of a list with corresponding score for the same index as the string. +This approach uses a string and a [list][list], both of these data types belongs to the parent data type [sequences][sequence]. +The code starts with defining a string constant with letters. +Then another constant is definded which is a list with corresponding score for the same index as the string. The `score` function takes a word as a parameter. -And uses the same [generator expression][generator-expersion] as the [dictionary approach][dictionary-approach]. +And uses the same [generator expression][generator-expersion] as the [dictionary approach][dictionary-approach] with some slight modifications. -The difference is that instead of using a [dictionary][dictionary] and looked up the score inside. +The difference is that instead of using a [dictionary][dictionary] and looking up the score inside of a dictonary. This approach gets the index of the letter in the KEYS constant and then then looks up the value for that index in SCORES list. Then takes that value and return that to the generator expression. From 906676a8d06090b7bcb6222603c7b6ae456044d9 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Wed, 11 Jan 2023 21:42:05 +0100 Subject: [PATCH 332/826] Apply suggestions from code review Co-authored-by: BethanyG --- .../.approaches/dictionary/content.md | 76 ++++++++++--------- .../.approaches/enum/content.md | 44 ++++++----- .../.approaches/introduction.md | 64 ++++++---------- .../.approaches/nested-tuple/content.md | 24 +++--- .../.approaches/two-sequences/content.md | 18 ++--- 5 files changed, 107 insertions(+), 119 deletions(-) diff --git a/exercises/practice/scrabble-score/.approaches/dictionary/content.md b/exercises/practice/scrabble-score/.approaches/dictionary/content.md index b046481804..d220150eb3 100644 --- a/exercises/practice/scrabble-score/.approaches/dictionary/content.md +++ b/exercises/practice/scrabble-score/.approaches/dictionary/content.md @@ -2,50 +2,52 @@ ```python LETTER_SCORES = { - 'A': 1, - 'E': 1, - 'I': 1, - 'O': 1, - 'U': 1, - 'L': 1, - 'N': 1, - 'R': 1, - 'S': 1, - 'T': 1, - 'D': 2, - 'G': 2, - 'B': 3, - 'C': 3, - 'M': 3, - 'P': 3, - 'F': 4, - 'H': 4, - 'V': 4, - 'W': 4, - 'Y': 4, - 'K': 5, - 'J': 8, - 'X': 8, - 'Q': 10, - 'Z': 10 + 'A': 1, 'E': 1, 'I': 1, 'O': 1, 'U': 1, + 'L': 1, 'N': 1, 'R': 1, 'S': 1, 'T': 1, + 'D': 2, 'G': 2, 'B': 3, 'C': 3, 'M': 3, + 'P': 3, 'F': 4, 'H': 4, 'V': 4, 'W': 4, + 'Y': 4, 'K': 5, 'J': 8, 'X': 8, 'Q': 10, 'Z': 10 } - def score(word): - return sum(LETTER_SCORES[letter.upper()] for letter in word) + return sum(LETTER_SCORES[letter] for letter in word.upper()) ``` -The code starts with initializing a constant that is a [dictionary][dictionary] there each letter is a key and their respective score as a value. -Then a function is defined that takes a word as an argument. +This code starts with defining a constant LETTER_SCORES as a dictionary ([concept:python/dicts](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict)) where each letter is a key and the corresponding score is a value. +Then the `score` function is defined, which takes a `` as an argument. + +The function returns the total score for the word using the built-in function [`sum`][sum]. + Sum is passed a [generator expression][generator-expression] that iterates over the letters in the word, looking up each score in LETTER_SCORES. +The generator expression produces the score values on the fly. +This means that it doesn't use memory to store all the values from LETTER_SCORES. +Instead, each value is looked up as needed by `sum`. + +Within the generator expression, the word is converted from lower to uppercase. +Each letter of the word is looked up in LETTER_SCORES, and the score value is yielded to `sum` as `sum` iterates over the expression. +This is almost exactly the same process as using a `list comprehension`. +However, a `list comprehension` would look up the values and save them into a `list` in memory. +`sum` would then "unpack" or iterate over the `list`. + +A variation on this dictionary approach is to use a dictionary transposition. + +```python +LETTER_SCORES = { + 1: {'A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T'}, + 2: {'D', 'G'}, + 3: {'B', 'C', 'M', 'P'}, + 4: {'F', 'H', 'V', 'W', 'Y'}, + 5: {'K'}, + 8: {'J', 'X'}, + 10: {'Q', 'Z'} +} + +def score(word): + return sum(next(score for score, letters in LETTER_SCORES.items() if character in letters) for character in word.upper()) -The function returns the built in function [`sum`][sum] that takes a [generator expression][generator-expersion] that iterates over the letters in the word. -What a generator expression does is that it generates the values on the fly. -Meaning that it doesn't have to use a lot of memory since it uses the last value and generates the next value. -Under the generation a letter is given from the string, then it is converted to upcase, and then being looked up at inside of the dictionary and the value is returned. +However, transposing the dictionary so that the keys are the score and the values are the letters requires more computational calculation (_a loop within a loop_) and is harder to read. + Therefore, arranging the dictionary by letter is both more efficient and easier to understand. -There is also a very similar approach that uses a dictionary transposition. -Although that approach requires more computational calculation therefore is this approach more efficient. [dictionary]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict -[generator-expersion]: https://peps.python.org/pep-0289/ +[generator-expression]: https://peps.python.org/pep-0289/ [sum]: https://docs.python.org/3/library/functions.html#sum diff --git a/exercises/practice/scrabble-score/.approaches/enum/content.md b/exercises/practice/scrabble-score/.approaches/enum/content.md index cad5b6bdec..188f54000a 100644 --- a/exercises/practice/scrabble-score/.approaches/enum/content.md +++ b/exercises/practice/scrabble-score/.approaches/enum/content.md @@ -13,32 +13,40 @@ class Scrabble(IntEnum): Q = Z = 10 def score(word): - return sum(Scrabble[character.upper()] for character in word) -``` + return sum(Scrabble[character] for character in word.upper()) -This approach uses an [`enum`][enum] to define the score of each letter. -An `enum` or known as an **enumeration** is sets of named constant and is immutable. -`enum` was added to python standard library (_also known as stdlib_) in python 3.4. +This approach uses an [`Enum`][enum] to define the score of each letter. +An [`Enum`][enum] (_also known as an **enumeration**_) is an object with named attributes assigned unique values. +These attributes are referred to as the enumeration _members_. +`Enum`s can be iterated over to return their members in definition order. +Values can be accessed via index syntax using the member name (_similar to how a dictionary lookup works_) . +`Enum`s are immutable, and their members function as constants. +The `enum` module was added to python standard library (_also known as stdlib_) in Python 3.4. -This approach uses an [`intEnum`][int-enum] it works very similar to a normal `enum` but it has the added benefit that the values are integers. -Thereby acts like integers. +This approach uses an [`IntEnum`][int-enum]. +An `IntEnum` is very similar to an `Enum`, but restricts assigned values to `int`s. +This allows the `IntEnum` to act as a collection of integers. +In fact, `IntEnum`s are considered subclasses of `int`s. -To use an `intEnum` you need to [import][import] it using: `from enum import IntEnum`. -Then you can define the `enum` class. +To use an `IntEnum` you need to first [import][import] it using: `from enum import IntEnum`. +Then you can define your `IntEnum` subclass. -The `enum` class is defined by using the [`class`][classes] keyword. -Then you need to specify the name of the class. +The `IntEnum` subclass is defined by using the [`class`][classes] keyword, followed by the name you are using for the class, and then the `IntEnum` class you are subclassing in parenthesis: -After that is the constant in the enum declared by giving the constant capital letters and the value is assigned by using the `=` operator. -This approach works by giving all the letters as constants and then value of the constant is the score of the letter. -After the `enum` is defined, the `score` function is defined. +```python +class ClassName(IntEnum): + +Member names are declared as constants (ALL CAPS) and assigned values using the `=` operator. + +This approach works by creating all the uppercase letters as members with their values being the score. +After the `IntEnum` is defined, the `score` function is defined. -The `score` function takes a word as a parameter. -And uses the same [generator expression][generator-expersion] as the [dictionary approach][dictionary-approach] but with a slight modification. -Which is that instead of looking up the value in a dictionary it looks it up in the `enum` class. +The `score` function takes a word as an argument. +The `score` function uses the same [generator expression][generator-expression] as the [dictionary approach][dictionary-approach], but with a slight modification. +Instead of looking up the value in a _dictionary_, it looks up the `InEnum` class member value. [classes]: https://docs.python.org/3/tutorial/classes.html [enum]: https://docs.python.org/3/library/enum.html -[generator-expersion]: https://peps.python.org/pep-0289/ +[generator-expression]: https://peps.python.org/pep-0289/ [int-enum]: https://docs.python.org/3/library/enum.html#enum.IntEnum [import]: https://docs.python.org/3/reference/import.html diff --git a/exercises/practice/scrabble-score/.approaches/introduction.md b/exercises/practice/scrabble-score/.approaches/introduction.md index 2891e55a6d..1b94ede3fe 100644 --- a/exercises/practice/scrabble-score/.approaches/introduction.md +++ b/exercises/practice/scrabble-score/.approaches/introduction.md @@ -1,73 +1,52 @@ # Introduction There are various ways to solve `scrabble-score`. -This approache document shows different strategies to solve this exercise. +This approaches document shows different strategies to solve this exercise. ## General guidance The goal of this exercise is to write a function that calculates the scrabble score for a given word. -The problem is that the scrabble score is calculated by the sum of the scores of each letter in the word. +The challenge is that the scrabble score is calculated by summing the scores of individual letters in a word. +The student needs to find an efficient and easily accessed way to store individual letter scores for lookup when processing different words. ## Approach: Using a single dictionary -Using a single dictionary is an approach, it is simple and fast. -It is also very pythonic. +Using a single dictionary for letter lookup is simple and fast. +It is also very pythonic, and could be considered the canonical approach to this exercise. ```python LETTER_SCORES = { - 'A': 1, - 'E': 1, - 'I': 1, - 'O': 1, - 'U': 1, - 'L': 1, - 'N': 1, - 'R': 1, - 'S': 1, - 'T': 1, - 'D': 2, - 'G': 2, - 'B': 3, - 'C': 3, - 'M': 3, - 'P': 3, - 'F': 4, - 'H': 4, - 'V': 4, - 'W': 4, - 'Y': 4, - 'K': 5, - 'J': 8, - 'X': 8, - 'Q': 10, - 'Z': 10 + 'A': 1, 'E': 1, 'I': 1, 'O': 1, 'U': 1, + 'L': 1, 'N': 1, 'R': 1, 'S': 1, 'T': 1, + 'D': 2, 'G': 2, 'B': 3, 'C': 3, 'M': 3, + 'P': 3, 'F': 4, 'H': 4, 'V': 4, 'W': 4, + 'Y': 4, 'K': 5, 'J': 8, 'X': 8, 'Q': 10, 'Z': 10 } def score(word): - return sum(LETTER_SCORES[letter.upper()] for letter in word) -``` + return sum(LETTER_SCORES[letter] for letter in word.upper()) For more information, check the [Dictionary Approach][dictionary-approach]. ## Approach: Using two sequences -Using two sequences is an approach, it removes the need of using a nested data structure or a dictonary. -Although the reason you might not want to do this is that it is hard to read. +Using two sequences removes the need to use a nested data structure or a dictionary. +Although you might not want to use this approach because it is hard to read and maintain. ```python KEYS = "AEIOULNRSTDGBCMPFHVWYKJXQZ" SCORES = [1] * 10 + [2] * 2 + [3] * 4 + [4] * 5 + [5] * 1 + [8] * 2 +[10] * 2 def score(word): - return sum(SCORES[KEYS.index(letter.upper())] for letter in word) + return sum(SCORES[KEYS.index(letter] for letter in word.upper()) ``` For more information, check the [Two Sequences Approach][two-sequences-approach]. ## Approach: Enum -Using an `enum` is an approach, it is short and easy to read. -Although it is more complicated since it uses a oop (object oriented programmering) elements. +Using an `Enum` is is short and easy to read. +Although creating an `Enum` can be more complicated since it uses OOP (object oriented programming). ```python from enum import IntEnum @@ -82,15 +61,16 @@ class Scrabble(IntEnum): Q = Z = 10 def score(word): - return sum(Scrabble[letter.upper()] for letter in word) + return sum(Scrabble[letter] for letter in word.upper()) ``` For more information, check the [Enum Approach][enum-approach]. ## Approach: Using a nested tuple -Tuples in python is more memory efficent than using a dictonary in python. -Although this solution since it is iterating over a tuple for every letter so is it slower. +Using a tuple in Python is generally more memory efficient than using a dictionary. +However, this solution requires iterating over the entire `tuple` for every letter in order to score a full word. +This makes the solution slower than the dictionary approach. ```python LETTERS_OF_SCORE = ( @@ -104,8 +84,8 @@ LETTERS_OF_SCORE = ( ) def score(word): - return sum(for character in word for letters, score in LETTERS_OF_SCORE if character in letters) -``` + return sum(score for character in word.upper() for + letters, score in LETTERS_OF_SCORE if character in letters) For more information, check the [Nested Tuple Approach][nested-tuple-approach]. diff --git a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md index 4498d1841f..113c385f0a 100644 --- a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md +++ b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md @@ -12,23 +12,23 @@ LETTERS_OF_SCORE = ( ) def score(word): - return sum(score for character in word for letters, score in LETTERS_OF_SCORE if character.upper() in letters) -``` + return sum(score for character in word.upper() for + letters, score in LETTERS_OF_SCORE if character in letters) -The code starts with initializing a constant with a [tuple][tuple] of tuples (_also known as a nested tuple_). -Inside of the inner tuples there is 2 values, the first value is a string of letters and the second value is the score for the letters. +The code starts with defining a constant, LETTERS_OF_SCORE as a [`tuple`][tuple] of tuples (_also known as a nested tuple_). +Inside of the inner tuples are 2 values, the first value is a string of letters and the second value is the score for those letters. -Then a function is defined that takes a word as an argument. -The function returns a [generator expression][generator-expersion] similar to the [dictionary approach][dictionary-approach] but has some slight modifcations. +Next, the `score` function is defined, taking a word as an argument. +The `score` function uses a [generator expression][generator-expression] similar to the [dictionary approach][dictionary-approach] with some slight modifications. -The difference is that this one uses a nested [for loop][for-loop] to iterate over the letters and the tuples. -We first iterate over the characters in the word and then iterate over the tuples. -Which means that for each letter are we iterating over all of the tuples. -There the tuple is unpacked into the letters and the score. +This particular approach uses a _nested_ [for loop][for-loop] to iterate over the letters and the tuples. +We first iterate over the characters in the word and then the tuples. +Which means that for **_each letter_** we iterate over **all** of the tuples. +Each iteration, the tuple is unpacked into the letters and their corresponding score. You can read more about unpacking in the [concept:python/unpacking-and-multiple-assignment](). -Then we check if the character is in the letters and if it is we return the score. +Then the code checks if the character is in the unpacked letters and if it is we return its score. [tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences -[generator-expersion]: https://peps.python.org/pep-0289/ +[generator-expression]: https://peps.python.org/pep-0289/ [for-loop]: https://realpython.com/python-for-loop/ diff --git a/exercises/practice/scrabble-score/.approaches/two-sequences/content.md b/exercises/practice/scrabble-score/.approaches/two-sequences/content.md index 944da04298..83f0ea1359 100644 --- a/exercises/practice/scrabble-score/.approaches/two-sequences/content.md +++ b/exercises/practice/scrabble-score/.approaches/two-sequences/content.md @@ -5,19 +5,17 @@ KEYS = "AEIOULNRSTDGBCMPFHVWYKJXQZ" SCORES = [1] * 10 + [2] * 2 + [3] * 4 + [4] * 5 + [5] * 1 + [8] * 2 + [10] * 2 def score(word): - return sum(SCORES[KEYS.index(letter.upper())] for letter in word) -``` + return sum(SCORES[KEYS.index(letter)] for letter in word.upper()) -This approach uses a string and a [list][list], both of these data types belongs to the parent data type [sequences][sequence]. -The code starts with defining a string constant with letters. -Then another constant is definded which is a list with corresponding score for the same index as the string. +This approach uses a string and a [list][list], both of which are [sequence][sequence] types. +The code begins by defining a string constant with letters. +Then another constant is defined as a list with the corresponding letter score at the same index as the letter in the string. -The `score` function takes a word as a parameter. -And uses the same [generator expression][generator-expersion] as the [dictionary approach][dictionary-approach] with some slight modifications. +The `score` function takes a word as an argument. +And uses the same [generator expression][generator-expression] as the [dictionary approach][dictionary-approach] with some slight modifications. -The difference is that instead of using a [dictionary][dictionary] and looking up the score inside of a dictonary. -This approach gets the index of the letter in the KEYS constant and then then looks up the value for that index in SCORES list. -Then takes that value and return that to the generator expression. +Instead of using a [dictionary][dictionary] and looking up the score, this approach looks up the index of the letter in the KEYS constant and then then looks up the value for that index in SCORES list within the generator expression. +These values are then added up by `sum`. [dictionary]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [dictionary-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/dictionary From b3f23ce0ae879ce3fc4710ee864322ae2c514101 Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 11 Jan 2023 21:48:49 +0100 Subject: [PATCH 333/826] Fixed spacing --- .../scrabble-score/.approaches/dictionary/content.md | 9 ++++----- .../scrabble-score/.approaches/enum/content.md | 8 +++++--- .../scrabble-score/.approaches/introduction.md | 10 ++++++---- .../scrabble-score/.approaches/nested-tuple/content.md | 7 ++++--- .../.approaches/two-sequences/content.md | 1 + 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/exercises/practice/scrabble-score/.approaches/dictionary/content.md b/exercises/practice/scrabble-score/.approaches/dictionary/content.md index d220150eb3..f87cce0ec1 100644 --- a/exercises/practice/scrabble-score/.approaches/dictionary/content.md +++ b/exercises/practice/scrabble-score/.approaches/dictionary/content.md @@ -16,10 +16,10 @@ This code starts with defining a constant LETTER_SCORES as a dictionary ([concep Then the `score` function is defined, which takes a `` as an argument. The function returns the total score for the word using the built-in function [`sum`][sum]. - Sum is passed a [generator expression][generator-expression] that iterates over the letters in the word, looking up each score in LETTER_SCORES. +Sum is passed a [generator expression][generator-expression] that iterates over the letters in the word, looking up each score in LETTER_SCORES. The generator expression produces the score values on the fly. This means that it doesn't use memory to store all the values from LETTER_SCORES. -Instead, each value is looked up as needed by `sum`. +Instead, each value is looked up as needed by `sum`. Within the generator expression, the word is converted from lower to uppercase. Each letter of the word is looked up in LETTER_SCORES, and the score value is yielded to `sum` as `sum` iterates over the expression. @@ -42,11 +42,10 @@ LETTER_SCORES = { def score(word): return sum(next(score for score, letters in LETTER_SCORES.items() if character in letters) for character in word.upper()) - +``` However, transposing the dictionary so that the keys are the score and the values are the letters requires more computational calculation (_a loop within a loop_) and is harder to read. - Therefore, arranging the dictionary by letter is both more efficient and easier to understand. - +Therefore, arranging the dictionary by letter is both more efficient and easier to understand. [dictionary]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [generator-expression]: https://peps.python.org/pep-0289/ diff --git a/exercises/practice/scrabble-score/.approaches/enum/content.md b/exercises/practice/scrabble-score/.approaches/enum/content.md index 188f54000a..5c2ad3a18a 100644 --- a/exercises/practice/scrabble-score/.approaches/enum/content.md +++ b/exercises/practice/scrabble-score/.approaches/enum/content.md @@ -14,6 +14,7 @@ class Scrabble(IntEnum): def score(word): return sum(Scrabble[character] for character in word.upper()) +``` This approach uses an [`Enum`][enum] to define the score of each letter. An [`Enum`][enum] (_also known as an **enumeration**_) is an object with named attributes assigned unique values. @@ -24,7 +25,7 @@ Values can be accessed via index syntax using the member name (_similar to how a The `enum` module was added to python standard library (_also known as stdlib_) in Python 3.4. This approach uses an [`IntEnum`][int-enum]. -An `IntEnum` is very similar to an `Enum`, but restricts assigned values to `int`s. +An `IntEnum` is very similar to an `Enum`, but restricts assigned values to `int`s. This allows the `IntEnum` to act as a collection of integers. In fact, `IntEnum`s are considered subclasses of `int`s. @@ -35,15 +36,16 @@ The `IntEnum` subclass is defined by using the [`class`][classes] keyword, follo ```python class ClassName(IntEnum): +``` Member names are declared as constants (ALL CAPS) and assigned values using the `=` operator. -This approach works by creating all the uppercase letters as members with their values being the score. +This approach works by creating all the uppercase letters as members with their values being the score. After the `IntEnum` is defined, the `score` function is defined. The `score` function takes a word as an argument. The `score` function uses the same [generator expression][generator-expression] as the [dictionary approach][dictionary-approach], but with a slight modification. -Instead of looking up the value in a _dictionary_, it looks up the `InEnum` class member value. +Instead of looking up the value in a _dictionary_, it looks up the `InEnum` class member value. [classes]: https://docs.python.org/3/tutorial/classes.html [enum]: https://docs.python.org/3/library/enum.html diff --git a/exercises/practice/scrabble-score/.approaches/introduction.md b/exercises/practice/scrabble-score/.approaches/introduction.md index 1b94ede3fe..8539d59d43 100644 --- a/exercises/practice/scrabble-score/.approaches/introduction.md +++ b/exercises/practice/scrabble-score/.approaches/introduction.md @@ -25,6 +25,7 @@ LETTER_SCORES = { def score(word): return sum(LETTER_SCORES[letter] for letter in word.upper()) +``` For more information, check the [Dictionary Approach][dictionary-approach]. @@ -38,7 +39,7 @@ KEYS = "AEIOULNRSTDGBCMPFHVWYKJXQZ" SCORES = [1] * 10 + [2] * 2 + [3] * 4 + [4] * 5 + [5] * 1 + [8] * 2 +[10] * 2 def score(word): - return sum(SCORES[KEYS.index(letter] for letter in word.upper()) + return sum(SCORES[KEYS.index(letter)] for letter in word.upper()) ``` For more information, check the [Two Sequences Approach][two-sequences-approach]. @@ -46,7 +47,7 @@ For more information, check the [Two Sequences Approach][two-sequences-approach] ## Approach: Enum Using an `Enum` is is short and easy to read. -Although creating an `Enum` can be more complicated since it uses OOP (object oriented programming). +Although creating an `Enum` can be more complicated since it uses OOP (object oriented programming). ```python from enum import IntEnum @@ -84,8 +85,9 @@ LETTERS_OF_SCORE = ( ) def score(word): - return sum(score for character in word.upper() for - letters, score in LETTERS_OF_SCORE if character in letters) + return sum(score for character in word.upper() for + letters, score in LETTERS_OF_SCORE if character in letters) +``` For more information, check the [Nested Tuple Approach][nested-tuple-approach]. diff --git a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md index 113c385f0a..70dc860a0a 100644 --- a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md +++ b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md @@ -12,10 +12,11 @@ LETTERS_OF_SCORE = ( ) def score(word): - return sum(score for character in word.upper() for + return sum(score for character in word.upper() for letters, score in LETTERS_OF_SCORE if character in letters) +``` -The code starts with defining a constant, LETTERS_OF_SCORE as a [`tuple`][tuple] of tuples (_also known as a nested tuple_). +The code starts with defining a constant, `LETTERS_OF_SCORE` as a [`tuple`][tuple] of tuples (_also known as a nested tuple_). Inside of the inner tuples are 2 values, the first value is a string of letters and the second value is the score for those letters. Next, the `score` function is defined, taking a word as an argument. @@ -29,6 +30,6 @@ You can read more about unpacking in the [concept:python/unpacking-and-multiple- Then the code checks if the character is in the unpacked letters and if it is we return its score. -[tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences [generator-expression]: https://peps.python.org/pep-0289/ [for-loop]: https://realpython.com/python-for-loop/ +[tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences diff --git a/exercises/practice/scrabble-score/.approaches/two-sequences/content.md b/exercises/practice/scrabble-score/.approaches/two-sequences/content.md index 83f0ea1359..6cbb6c7694 100644 --- a/exercises/practice/scrabble-score/.approaches/two-sequences/content.md +++ b/exercises/practice/scrabble-score/.approaches/two-sequences/content.md @@ -6,6 +6,7 @@ SCORES = [1] * 10 + [2] * 2 + [3] * 4 + [4] * 5 + [5] * 1 + [8] * 2 + [10] * 2 def score(word): return sum(SCORES[KEYS.index(letter)] for letter in word.upper()) +``` This approach uses a string and a [list][list], both of which are [sequence][sequence] types. The code begins by defining a string constant with letters. From 220fce386d72aea29a4813268ce72ec9b75676d4 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 26 Jan 2023 15:23:04 -0800 Subject: [PATCH 334/826] Updated Exercise docs, toml files, test files and example solutions for: (#3329) * Palindrom Products (toml and tests) * Roman Numerals (tests) * Phone Number (toml, tests, and example solution) * Resistor Color Trio (intro, toml, tests, and example solution) --- .../palindrome-products/.meta/tests.toml | 24 +++++++++---- .../palindrome_products_test.py | 5 +++ .../phone-number/.docs/instructions.append.md | 4 +-- .../practice/phone-number/.meta/example.py | 4 +-- .../practice/phone-number/.meta/tests.toml | 10 ++++++ .../phone-number/phone_number_test.py | 4 +-- .../resistor-color-trio/.docs/instructions.md | 8 +++-- .../resistor-color-trio/.meta/example.py | 36 +++++++++++++------ .../resistor-color-trio/.meta/tests.toml | 15 ++++++++ .../resistor_color_trio_test.py | 15 ++++++++ .../roman-numerals/roman_numerals_test.py | 30 ++++++++-------- 11 files changed, 113 insertions(+), 42 deletions(-) diff --git a/exercises/practice/palindrome-products/.meta/tests.toml b/exercises/practice/palindrome-products/.meta/tests.toml index b34cb0d475..a3bc41750a 100644 --- a/exercises/practice/palindrome-products/.meta/tests.toml +++ b/exercises/practice/palindrome-products/.meta/tests.toml @@ -1,12 +1,19 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [5cff78fe-cf02-459d-85c2-ce584679f887] -description = "finds the smallest palindrome from single digit factors" +description = "find the smallest palindrome from single digit factors" [0853f82c-5fc4-44ae-be38-fadb2cced92d] -description = "finds the largest palindrome from single digit factors" +description = "find the largest palindrome from single digit factors" [66c3b496-bdec-4103-9129-3fcb5a9063e1] description = "find the smallest palindrome from double digit factors" @@ -15,13 +22,13 @@ description = "find the smallest palindrome from double digit factors" description = "find the largest palindrome from double digit factors" [cecb5a35-46d1-4666-9719-fa2c3af7499d] -description = "find smallest palindrome from triple digit factors" +description = "find the smallest palindrome from triple digit factors" [edab43e1-c35f-4ea3-8c55-2f31dddd92e5] description = "find the largest palindrome from triple digit factors" [4f802b5a-9d74-4026-a70f-b53ff9234e4e] -description = "find smallest palindrome from four digit factors" +description = "find the smallest palindrome from four digit factors" [787525e0-a5f9-40f3-8cb2-23b52cf5d0be] description = "find the largest palindrome from four digit factors" @@ -37,3 +44,6 @@ description = "error result for smallest if min is more than max" [eeeb5bff-3f47-4b1e-892f-05829277bd74] description = "error result for largest if min is more than max" + +[16481711-26c4-42e0-9180-e2e4e8b29c23] +description = "smallest product does not use the smallest factor" diff --git a/exercises/practice/palindrome-products/palindrome_products_test.py b/exercises/practice/palindrome-products/palindrome_products_test.py index 2ff69adc2d..0fd7eaece3 100644 --- a/exercises/practice/palindrome-products/palindrome_products_test.py +++ b/exercises/practice/palindrome-products/palindrome_products_test.py @@ -71,5 +71,10 @@ def test_error_result_for_largest_if_min_is_more_than_max(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "min must be <= max") + def test_smallest_product_does_not_use_the_smallest_factor(self): + value, factors = smallest(min_factor=3215, max_factor=4000) + self.assertEqual(value, 10988901) + self.assertFactorsEqual(factors, [[3297, 3333]]) + def assertFactorsEqual(self, actual, expected): self.assertEqual(set(map(frozenset, actual)), set(map(frozenset, expected))) diff --git a/exercises/practice/phone-number/.docs/instructions.append.md b/exercises/practice/phone-number/.docs/instructions.append.md index 1001c9dc7e..662c8c8762 100644 --- a/exercises/practice/phone-number/.docs/instructions.append.md +++ b/exercises/practice/phone-number/.docs/instructions.append.md @@ -10,10 +10,10 @@ To raise a `ValueError` with a message, write the message as an argument to the ```python # if a phone number has less than 10 digits. -raise ValueError("incorrect number of digits") +raise ValueError("must not be fewer than 10 digits") # if a phone number has more than 11 digits. -raise ValueError("more than 11 digits") +raise ValueError("must not be greater than 11 digits") # if a phone number has 11 digits, but starts with a number other than 1. raise ValueError("11 digits must start with 1") diff --git a/exercises/practice/phone-number/.meta/example.py b/exercises/practice/phone-number/.meta/example.py index 02b8e13b8b..d23102a01e 100644 --- a/exercises/practice/phone-number/.meta/example.py +++ b/exercises/practice/phone-number/.meta/example.py @@ -25,10 +25,10 @@ def _clean(self, number): def _normalize(self, number): if len(number) < 10: - raise ValueError('incorrect number of digits') + raise ValueError('must not be fewer than 10 digits') if len(number) > 11: - raise ValueError('more than 11 digits') + raise ValueError('must not be greater than 11 digits') if len(number) == 10 or len(number) == 11 and number.startswith('1'): if number[-10] == '0': diff --git a/exercises/practice/phone-number/.meta/tests.toml b/exercises/practice/phone-number/.meta/tests.toml index ee308c3e59..24dbf07a76 100644 --- a/exercises/practice/phone-number/.meta/tests.toml +++ b/exercises/practice/phone-number/.meta/tests.toml @@ -20,6 +20,11 @@ description = "cleans numbers with multiple spaces" [598d8432-0659-4019-a78b-1c6a73691d21] description = "invalid when 9 digits" +include = false + +[2de74156-f646-42b5-8638-0ef1d8b58bc2] +description = "invalid when 9 digits" +reimplements = "598d8432-0659-4019-a78b-1c6a73691d21" [57061c72-07b5-431f-9766-d97da7c4399d] description = "invalid when 11 digits does not start with a 1" @@ -32,6 +37,11 @@ description = "valid when 11 digits and starting with 1 even with punctuation" [c6a5f007-895a-4fc5-90bc-a7e70f9b5cad] description = "invalid when more than 11 digits" +include = false + +[4a1509b7-8953-4eec-981b-c483358ff531] +description = "invalid when more than 11 digits" +reimplements = "c6a5f007-895a-4fc5-90bc-a7e70f9b5cad" [63f38f37-53f6-4a5f-bd86-e9b404f10a60] description = "invalid with letters" diff --git a/exercises/practice/phone-number/phone_number_test.py b/exercises/practice/phone-number/phone_number_test.py index dfbb9f851a..72ff3b099a 100644 --- a/exercises/practice/phone-number/phone_number_test.py +++ b/exercises/practice/phone-number/phone_number_test.py @@ -24,7 +24,7 @@ def test_invalid_when_9_digits(self): with self.assertRaises(ValueError) as err: PhoneNumber("123456789") self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "incorrect number of digits") + self.assertEqual(err.exception.args[0], "must not be fewer than 10 digits") def test_invalid_when_11_digits_does_not_start_with_a_1(self): with self.assertRaises(ValueError) as err: @@ -44,7 +44,7 @@ def test_invalid_when_more_than_11_digits(self): with self.assertRaises(ValueError) as err: PhoneNumber("321234567890") self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "more than 11 digits") + self.assertEqual(err.exception.args[0], "must not be greater than 11 digits") def test_invalid_with_letters(self): with self.assertRaises(ValueError) as err: diff --git a/exercises/practice/resistor-color-trio/.docs/instructions.md b/exercises/practice/resistor-color-trio/.docs/instructions.md index fcc76958a5..4ad2aede37 100644 --- a/exercises/practice/resistor-color-trio/.docs/instructions.md +++ b/exercises/practice/resistor-color-trio/.docs/instructions.md @@ -46,9 +46,11 @@ So an input of `"orange", "orange", "black"` should return: > "33 ohms" -When we get more than a thousand ohms, we say "kiloohms". -That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. +When we get to larger resistors, a [metric prefix][metric-prefix] is used to indicate a larger magnitude of ohms, such as "kiloohms". +That is similar to saying "2 kilometers" instead of "2000 meters", or "2 kilograms" for "2000 grams". -So an input of `"orange", "orange", "orange"` should return: +For example, an input of `"orange", "orange", "orange"` should return: > "33 kiloohms" + +[metric-prefix]: https://en.wikipedia.org/wiki/Metric_prefix diff --git a/exercises/practice/resistor-color-trio/.meta/example.py b/exercises/practice/resistor-color-trio/.meta/example.py index a6588ea5a6..69554592d0 100644 --- a/exercises/practice/resistor-color-trio/.meta/example.py +++ b/exercises/practice/resistor-color-trio/.meta/example.py @@ -1,18 +1,32 @@ COLORS = [ - 'black', - 'brown', - 'red', - 'orange', - 'yellow', - 'green', - 'blue', - 'violet', - 'grey', - 'white' + 'black', + 'brown', + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'violet', + 'grey', + 'white' ] def label(colors): value = 10 * COLORS.index(colors[0]) + COLORS.index(colors[1]) value *= 10 ** COLORS.index(colors[2]) - return str(value) + ' ohms' if value < 1000 else str(value // 1000) + ' kiloohms' + label = str(value) + + if len(label) < 4 : + unit = 'ohms' + elif len(label) < 7: + label = str(value//1000) + unit = 'kiloohms' + elif len(label) <= 8 : + label = str(value//1000000) + unit = 'megaohms' + elif len(label) >= 9: + label = str(value//1000000000) + unit = 'gigaohms' + + return f'{value if value < 1000 else label} {unit}' diff --git a/exercises/practice/resistor-color-trio/.meta/tests.toml b/exercises/practice/resistor-color-trio/.meta/tests.toml index dc6077e54f..b7d45fa5d5 100644 --- a/exercises/practice/resistor-color-trio/.meta/tests.toml +++ b/exercises/practice/resistor-color-trio/.meta/tests.toml @@ -23,3 +23,18 @@ description = "Green and brown and orange" [f5d37ef9-1919-4719-a90d-a33c5a6934c9] description = "Yellow and violet and yellow" + +[5f6404a7-5bb3-4283-877d-3d39bcc33854] +description = "Blue and violet and blue" + +[7d3a6ab8-e40e-46c3-98b1-91639fff2344] +description = "Minimum possible value" + +[ca0aa0ac-3825-42de-9f07-dac68cc580fd] +description = "Maximum possible value" + +[0061a76c-903a-4714-8ce2-f26ce23b0e09] +description = "First two colors make an invalid octal number" + +[30872c92-f567-4b69-a105-8455611c10c4] +description = "Ignore extra colors" diff --git a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py index 302df179ef..ddfdfb6930 100644 --- a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py +++ b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py @@ -22,3 +22,18 @@ def test_green_and_brown_and_orange(self): def test_yellow_and_violet_and_yellow(self): self.assertEqual(label(["yellow", "violet", "yellow"]), "470 kiloohms") + + def test_blue_and_violet_and_blue(self): + self.assertEqual(label(["blue", "violet", "blue"]), "67 megaohms") + + def test_minimum_possible_value(self): + self.assertEqual(label(["black", "black", "black"]), "0 ohms") + + def test_maximum_possible_value(self): + self.assertEqual(label(["white", "white", "white"]), "99 gigaohms") + + def test_first_two_colors_make_an_invalid_octal_number(self): + self.assertEqual(label(["black", "grey", "black"]), "8 ohms") + + def test_ignore_extra_colors(self): + self.assertEqual(label(["blue", "green", "yellow", "orange"]), "650 kiloohms") diff --git a/exercises/practice/roman-numerals/roman_numerals_test.py b/exercises/practice/roman-numerals/roman_numerals_test.py index f85422cc5e..59e5e697fa 100644 --- a/exercises/practice/roman-numerals/roman_numerals_test.py +++ b/exercises/practice/roman-numerals/roman_numerals_test.py @@ -27,6 +27,9 @@ def test_6_is_vi(self): def test_9_is_ix(self): self.assertEqual(roman(9), "IX") + def test_16_is_xvi(self): + self.assertEqual(roman(16), "XVI") + def test_27_is_xxvii(self): self.assertEqual(roman(27), "XXVII") @@ -39,6 +42,9 @@ def test_49_is_xlix(self): def test_59_is_lix(self): self.assertEqual(roman(59), "LIX") + def test_66_is_lxvi(self): + self.assertEqual(roman(66), "LXVI") + def test_93_is_xciii(self): self.assertEqual(roman(93), "XCIII") @@ -48,36 +54,30 @@ def test_141_is_cxli(self): def test_163_is_clxiii(self): self.assertEqual(roman(163), "CLXIII") + def test_166_is_clxvi(self): + self.assertEqual(roman(166), "CLXVI") + def test_402_is_cdii(self): self.assertEqual(roman(402), "CDII") def test_575_is_dlxxv(self): self.assertEqual(roman(575), "DLXXV") + def test_666_is_dclxvi(self): + self.assertEqual(roman(666), "DCLXVI") + def test_911_is_cmxi(self): self.assertEqual(roman(911), "CMXI") def test_1024_is_mxxiv(self): self.assertEqual(roman(1024), "MXXIV") - def test_3000_is_mmm(self): - self.assertEqual(roman(3000), "MMM") - - def test_16_is_xvi(self): - self.assertEqual(roman(16), "XVI") - - def test_66_is_lxvi(self): - self.assertEqual(roman(66), "LXVI") - - def test_166_is_clxvi(self): - self.assertEqual(roman(166), "CLXVI") - - def test_666_is_dclxvi(self): - self.assertEqual(roman(666), "DCLXVI") - def test_1666_is_mdclxvi(self): self.assertEqual(roman(1666), "MDCLXVI") + def test_3000_is_mmm(self): + self.assertEqual(roman(3000), "MMM") + def test_3001_is_mmmi(self): self.assertEqual(roman(3001), "MMMI") From afc8e4f025774027a6668b3be12e1435e77dbd35 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Thu, 26 Jan 2023 06:55:54 -0600 Subject: [PATCH 335/826] Update introduction.md --- exercises/practice/grains/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/grains/.approaches/introduction.md b/exercises/practice/grains/.approaches/introduction.md index 13f56d33be..c02d5b8ba6 100644 --- a/exercises/practice/grains/.approaches/introduction.md +++ b/exercises/practice/grains/.approaches/introduction.md @@ -19,7 +19,7 @@ You can see that the exponent, or power, that `2` is raised by is always one les | 1 | 0 | 2 to the power of 0 = 1 | | 2 | 1 | 2 to the power of 1 = 2 | | 3 | 2 | 2 to the power of 2 = 4 | -| 4 | 3 | 2 to the power of 4 = 8 | +| 4 | 3 | 2 to the power of 3 = 8 | ## Approach: Bit-shifting From bf818b5b1c4705903a9e0fa69c891b936e077204 Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 11 Jan 2023 21:44:03 +0100 Subject: [PATCH 336/826] start --- .../.approaches/ascii-values/content.md | 45 ++++++++ .../.approaches/ascii-values/snippet.txt | 5 + .../rotational-cipher/.approaches/config.json | 22 ++++ .../.approaches/introduction.md | 109 ++++++++++++++++++ .../nested-for-loop-optimized/content.md | 92 +++++++++++++++ .../nested-for-loop-optimized/snippet.txt | 5 + 6 files changed, 278 insertions(+) create mode 100644 exercises/practice/rotational-cipher/.approaches/ascii-values/content.md create mode 100644 exercises/practice/rotational-cipher/.approaches/ascii-values/snippet.txt create mode 100644 exercises/practice/rotational-cipher/.approaches/config.json create mode 100644 exercises/practice/rotational-cipher/.approaches/introduction.md create mode 100644 exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/content.md create mode 100644 exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/snippet.txt diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md new file mode 100644 index 0000000000..0daf501c6a --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md @@ -0,0 +1,45 @@ +# Ascii + +```python +def rotate(text, key) + result = "" + for letter in text: + if letter.isalpha(): + if letter.isupper(): + result += chr((ord(letter) - 65 + key) % 26 + 65) + else: + result += chr((ord(letter) - 97 + key) % 26 + 97) + else: + result += letter + return result +``` + +This approach uses [ascii values][ascii], ascii stands for American Standard Code for Information Interchange. +It is a character encoding standard for electronic communication. +It is a 7-bit code, which means that it can represent 128 different characters. +The system uses numbers to represent various characters, symbols, and other entities. + +In ascii can you find all the downcased letter in the range between 97 and 123. +While the upcased letters are in the range between 65 and 91. + +The reason why you might not want to do this approach is that it only supports the english alphabet. + +The approach starts with defining the function `rotate`, then a variable `result` is defined with the value of an empty string. +Then is all the letters from the text iterated over through a `for loop`. +Then is checked if the letter is a letter, if it is a letter then is checked if it is a uppercased letter. +If it is a uppercased letter then is `ord` used which converts a letter to an ascii and is then added with the key and the ascii value of the letter subtracted with 65. +Then is the result of that modulo 26 added with 65. + +That is because we want to know which index in the alphabet the letter is. +And if the number is over 26 we want to make sure that it is in the range of 0-26. +So we use modulo to make sure it is in that range. +To use modulo for a range we have to make sure that it starts at zero, thereby are we subtracting the ascii value of the letter with 65. +After that to get the back to a letter we add 65 and use the `chr` method which converts an ascii value to a letter. +After that is the new letter added to the result. + +If the letter is a lowercased letter then is the same done but with the ascii value of 97 subtracted with the letter. + +If the letter is not a letter then is the letter added to the result. +When the loop is finished we return the result. + +[ascii]: https://en.wikipedia.org/wiki/ASCII diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/snippet.txt b/exercises/practice/rotational-cipher/.approaches/ascii-values/snippet.txt new file mode 100644 index 0000000000..f678cd824a --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/snippet.txt @@ -0,0 +1,5 @@ + for number_a in range(min_factor, max_factor+1): + for number_b in range(min_factor, max_factor+1): + if number_a * number_b >= result: + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: diff --git a/exercises/practice/rotational-cipher/.approaches/config.json b/exercises/practice/rotational-cipher/.approaches/config.json new file mode 100644 index 0000000000..3882c1cb97 --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/config.json @@ -0,0 +1,22 @@ +{ + "introduction": { + "authors": ["meatball133", "bethanyg"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "ec09a4e1-6bc3-465b-a366-8ccdd2dbe093", + "slug": "ascii-values", + "title": "ASCII values", + "blurb": "Use letters ascii value to rotate the alphabet", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "6eb99523-b12b-4e72-8ed8-3444b635b9e5", + "slug": "nested-for-loop-optimized", + "title": "Nested for loop optimized", + "blurb": "Nested for loop optimized edition", + "authors": ["meatball133", "bethanyg"] + } + ] +} diff --git a/exercises/practice/rotational-cipher/.approaches/introduction.md b/exercises/practice/rotational-cipher/.approaches/introduction.md new file mode 100644 index 0000000000..5c63d42303 --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/introduction.md @@ -0,0 +1,109 @@ +# Introduction + +There are various ways to solve `palindrome-products`. +This approaches document shows 2 _common_ strategies, with one being a lot more efficient than the other. +That being said, neither approach here is considered canonical, and other "pythonic" approaches could be added/expanded on in the future. + +## General guidance + +The goal of this exercise is to generate the largest and smallest palindromes from a given range of numbers. + +## Approach: Using ascii values + +This approach is very simple and easy to understand. +it uses the ascii value of the letters to rotate them. +There the numbers 65-91 in the ascii range represent downcased letters. +While 97-123 represent upcased letters. + +The reason why you might not want to do this approach is that it only supports the english alphabet. +Say we want to use the scandivanian letter: **ä**, then this approach will not work. +Since **ä** has the ascii value of 132. + +```python +def rotate(text, key) + result = "" + for letter in text: + if letter.isalpha(): + if letter.isupper(): + result += chr((ord(letter) - 65 + key) % 26 + 65) + else: + result += chr((ord(letter) - 97 + key) % 26 + 97) + else: + result += letter + return result +``` + +## Approach: Alphabet + +This approach is similar to the previous one, but instead of using the ascii values, it uses the index of the letter in the alphabet. +It requires the storing of a string and unless you are using two strings you have to convert the letters from upper to lower case. + +What this approach although give is the posiblity to use any alphabet. +Say we want to use the scandivanian letter: **ä**, then we just add it where we want it: +`abcdefghijklmnopqrstuvwxyzä` and it will rotate correctley around that. + +```python +AlPHABET = "abcdefghijklmnopqrstuvwxyz" + +def rotate(text, key): + result = "" + for letter in text: + if letter.isalpha(): + if letter.isupper(): + result += AlPHABET[(AlPHABET.index(letter.lower()) + key) % 26].upper() + else: + result += AlPHABET[(AlPHABET.index(letter) + key) % 26] + else: + result += letter + return result +``` + +For more information, check the [Nested for loop approach][approach-nested-for-loop]. + +## Approach: Str translate + +This approach is similar to the previous one, but instead of using the index of the letter in the alphabet, it uses the `str.translate` method. +The benefit of this approach is that it has no visable loop, thereby the code becomes more concise. +What to note is that the `str.translate` still loops over the `string` thereby even if it is no visable loop, it doesn't mean that a method is not looping. + +```python +AlPHABET = "abcdefghijklmnopqrstuvwxyz" + +def rotate(text, key): + translator = AlPHABET[key:] + AlPHABET[:key] + return text.translate(str.maketrans(AlPHABET + AlPHABET.upper(), translator + translator.upper())) +``` + +You can read more about how to achieve this optimization in: [Nested for loop optimized][approach-nested-for-loop-optimized]. + +## Approach: Recursion + +In this approach we use a recursive function. +A recursive function is a function that calls itself. +This approach can be more concise than other approaches, and may also be more readable for some audiences. + +The reason why you might not want to use this approach is that Python has a [recursion limit][recursion-limit] with a default of 1000. + +```python +AlPHABET = "abcdefghijklmnopqrstuvwxyz" + +def rotate(text, key): + if text == "": + return "" + first_letter, rest = text[0], text[1:] + if first_letter.isalpha(): + if first_letter.isupper(): + return AlPHABET[(AlPHABET.index(first_letter.lower()) + key) % 26].upper() + rotate(rest, key) + else: + return AlPHABET[(AlPHABET.index(first_letter) + key) % 26] + rotate(rest, key) + else: + return first_letter + rotate(rest, key) +``` + +## Benchmark + +For more information, check the [Performance article][article-performance]. + +[approach-nested-for-loop]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop +[approach-nested-for-loop-optimized]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop-optimized +[article-performance]: https://exercism.org/tracks/python/exercises/palindrome-products/articles/performance diff --git a/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/content.md b/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/content.md new file mode 100644 index 0000000000..2bc2718391 --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/content.md @@ -0,0 +1,92 @@ +# Nested For Loop Optimized + +This approach shows that just a few changes can improve the running time of the solution significantly. + +```python +def largest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(max_factor, min_factor - 1,-1): + was_bigger = False + for number_b in range(max_factor, number_a - 1, -1): + if number_a * number_b >= result: + was_bigger = True + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b > result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if not was_bigger: + break + if result == 0: + result = None + return (result, answer) + + +def smallest(min_factor, max_factor): + if min_factor > max_factor: + raise ValueError("min must be <= max") + result = 0 + answer = [] + for number_a in range(min_factor, max_factor+1): + was_smaller = False + for number_b in range(min_factor, max_factor+1): + if number_a * number_b <= result or result == 0: + was_smaller = True + test_value = str(number_a * number_b) + if test_value == test_value[::-1]: + if number_a * number_b < result: + answer = [] + result = int(test_value) + answer.append([number_a, number_b]) + if not was_smaller: + break + if result == 0: + result = None + return (result, answer) +``` + +This approach is very similar to the [nested for loop approach][approach-nested-for-loop], but it has a few optimizations. +To optimize the `largest` function, we have to start the inner loop from the maximum factor and proceed down to the minimum factor. +This allows us to stop the inner loop earlier than before. +We also set the minimum value in the _inner_ loop to the current value of the _outer_ loop. + +Here is an example of how the algorithm works and why the loops need to be modified. +Say we take maximum to be 99 and the minimum 10. +In the first round: + +``` +x = [99 , 98, 97 ...] +y = [99] +``` + +And already we have our result: `9009[91,99]` +Although the loops have to continue to make us sure there are no higher values. + +``` +x = [98, 97, 96 ...] +y = [99, 98] +... +x = [90, 89, 88 ...] +y = [99, 98,97,96,95,94,93,92,91,90] + +Here we can see that the highest value for this "run" is 90 \* 99 = 8910. +Meaning that running beyond this point won't give us any values higher than 9009. + +That is why we introduce the `was_bigger` variable. +With `was_bigger`, we can check if the inner loop has a bigger value than the current result. +If there has not been a bigger value, we can stop the inner loop and stop the outer loop. +We do that by using the [`break`][break] statement. + +If we hadn't modified the direction of the inner loop, it would have started from the minimum factor and gone up to the maximum factor. +This would mean that for every new run in the outer loop, the values would be bigger than the previous run. + +The `smallest` function is optimized in a similar way as `largest` function compared to the original approach. +The only difference is that we have to start the inner loop and outer loop from the minimum factor and go up to the maximum factor. +Since what we want is the smallest value, we have to start from the smallest value and go up to the biggest value. + +[approach-nested-for-loop]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop +[break]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops diff --git a/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/snippet.txt b/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/snippet.txt new file mode 100644 index 0000000000..bf91dd6753 --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/snippet.txt @@ -0,0 +1,5 @@ +for number_a in range(max_factor, min_factor - 1,-1): + was_bigger = False + for number_b in range(max_factor, number_a - 1, -1): + if number_a * number_b >= result: + was_bigger = True From 9580dd8ad684b8ff705a7e98e637862f679ac812 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 12 Jan 2023 22:21:29 +0100 Subject: [PATCH 337/826] Added preformance article and a bunch of content to approaches --- .../.approaches/alphabet/content.md | 46 ++++++++++ .../.approaches/alphabet/snippet.txt | 8 ++ .../.approaches/ascii-values/content.md | 21 +++-- .../.approaches/ascii-values/snippet.txt | 13 ++- .../rotational-cipher/.approaches/config.json | 20 +++- .../.approaches/introduction.md | 41 +++++---- .../nested-for-loop-optimized/content.md | 92 ------------------- .../nested-for-loop-optimized/snippet.txt | 5 - .../.approaches/recursion/content.md | 42 +++++++++ .../.approaches/recursion/snippet.txt | 8 ++ .../.approaches/str-translate/content.md | 54 +++++++++++ .../.approaches/str-translate/snippet.txt | 5 + .../rotational-cipher/.articles/config.json | 11 +++ .../.articles/performance/code/Benchmark.py | 90 ++++++++++++++++++ .../.articles/performance/content.md | 43 +++++++++ .../.articles/performance/snippet.md | 6 ++ 16 files changed, 377 insertions(+), 128 deletions(-) create mode 100644 exercises/practice/rotational-cipher/.approaches/alphabet/content.md create mode 100644 exercises/practice/rotational-cipher/.approaches/alphabet/snippet.txt delete mode 100644 exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/content.md delete mode 100644 exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/snippet.txt create mode 100644 exercises/practice/rotational-cipher/.approaches/recursion/content.md create mode 100644 exercises/practice/rotational-cipher/.approaches/recursion/snippet.txt create mode 100644 exercises/practice/rotational-cipher/.approaches/str-translate/content.md create mode 100644 exercises/practice/rotational-cipher/.approaches/str-translate/snippet.txt create mode 100644 exercises/practice/rotational-cipher/.articles/config.json create mode 100644 exercises/practice/rotational-cipher/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/rotational-cipher/.articles/performance/content.md create mode 100644 exercises/practice/rotational-cipher/.articles/performance/snippet.md diff --git a/exercises/practice/rotational-cipher/.approaches/alphabet/content.md b/exercises/practice/rotational-cipher/.approaches/alphabet/content.md new file mode 100644 index 0000000000..75d85c531e --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/alphabet/content.md @@ -0,0 +1,46 @@ +# Alphabet + +```python +AlPHABET = "abcdefghijklmnopqrstuvwxyz" + +def rotate(text, key): + result = "" + for letter in text: + if letter.isalpha(): + if letter.isupper(): + result += AlPHABET[(AlPHABET.index(letter.lower()) + key) % 26].upper() + else: + result += AlPHABET[(AlPHABET.index(letter) + key) % 26] + else: + result += letter + return result +``` + +The approach starts with defining the a constant which holds the whole alphabets lowercase letters. +After that the function `rotate` is declared, then a variable `result` is defined with the value of an empty string. + +Then is all the letters from the text argument iterated over through a [`for loop`][for-loop]. +Then is checked if the letter is a letter, if it is a letter then is checked if it is a uppercased letter. + +If it is a uppercased letter then it is converted to lowe case and finds its index in the `AlPHABET` constant. +Then is the key added to the index and [modulo (`%`)][modulo] 26 is used on the result. +Then is the letter at the index found in the `AlPHABET` constant and the letter is converted to upcase. + +If the letter is a lowercased letter then it does the same process but don't convert the letter to downcase and then to uppercase. + +If the letter is not a letter then is the letter added to the result. +When the loop is finished we return the result. + +If you only want to use english letters so could you import the alphabet instead of defining it yourself. +Since in the [`string`][string] module there is a constant called [`ascii_lowercase`][ascii_lowercase] which holds the lowercased alphabet. + +```python +import string + +AlPHABET = string.ascii_lowercase +``` + +[ascii_lowercase]: https://docs.python.org/3/library/string.html#string.ascii_letters +[for-loop]: https://realpython.com/python-for-loop/ +[modulo]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations +[string]: https://docs.python.org/3/library/string.html diff --git a/exercises/practice/rotational-cipher/.approaches/alphabet/snippet.txt b/exercises/practice/rotational-cipher/.approaches/alphabet/snippet.txt new file mode 100644 index 0000000000..ade372000b --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/alphabet/snippet.txt @@ -0,0 +1,8 @@ +for letter in text: + if letter.isalpha(): + if letter.isupper(): + result += AlPHABET[(AlPHABET.index(letter.lower()) + key) % 26].upper() + else: + result += AlPHABET[(AlPHABET.index(letter) + key) % 26] + else: + result += letter \ No newline at end of file diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md index 0daf501c6a..cb637198a9 100644 --- a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md @@ -1,7 +1,7 @@ # Ascii ```python -def rotate(text, key) +def rotate(text, key): result = "" for letter in text: if letter.isalpha(): @@ -25,16 +25,20 @@ While the upcased letters are in the range between 65 and 91. The reason why you might not want to do this approach is that it only supports the english alphabet. The approach starts with defining the function `rotate`, then a variable `result` is defined with the value of an empty string. -Then is all the letters from the text iterated over through a `for loop`. +Then is all the letters from the text argument iterated over through a [`for loop`][for-loop]. Then is checked if the letter is a letter, if it is a letter then is checked if it is a uppercased letter. -If it is a uppercased letter then is `ord` used which converts a letter to an ascii and is then added with the key and the ascii value of the letter subtracted with 65. -Then is the result of that modulo 26 added with 65. + +Python has a built in function called `ord` that converts a [unicode][unicode] symbol to an integer. +The unicode's first 128 characters are the same as ascii. + +If it is a uppercased letter then is [`ord`][ord] used to convert the letters to an integer and is then added with the key and then subtracted with 65. +Then is the result of that [modulo (`%`)][modulo] 26 added with 65. That is because we want to know which index in the alphabet the letter is. And if the number is over 26 we want to make sure that it is in the range of 0-26. So we use modulo to make sure it is in that range. -To use modulo for a range we have to make sure that it starts at zero, thereby are we subtracting the ascii value of the letter with 65. -After that to get the back to a letter we add 65 and use the `chr` method which converts an ascii value to a letter. +To use modulo for a range we have to make sure that it starts at zero, thereby are we subtracting the integer value of the letter with 65. +After that to get the back to a letter we add 65 and use the [`chr`][chr] method which converts an an unicode value to a letter. After that is the new letter added to the result. If the letter is a lowercased letter then is the same done but with the ascii value of 97 subtracted with the letter. @@ -43,3 +47,8 @@ If the letter is not a letter then is the letter added to the result. When the loop is finished we return the result. [ascii]: https://en.wikipedia.org/wiki/ASCII +[chr]: https://docs.python.org/3/library/functions.html#chr +[for-loop]: https://realpython.com/python-for-loop/ +[modulo]: https://realpython.com/python-modulo-operator/ +[ord]: https://docs.python.org/3/library/functions.html#ord +[unicode]: https://en.wikipedia.org/wiki/Unicode diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/snippet.txt b/exercises/practice/rotational-cipher/.approaches/ascii-values/snippet.txt index f678cd824a..ede3b5c989 100644 --- a/exercises/practice/rotational-cipher/.approaches/ascii-values/snippet.txt +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/snippet.txt @@ -1,5 +1,8 @@ - for number_a in range(min_factor, max_factor+1): - for number_b in range(min_factor, max_factor+1): - if number_a * number_b >= result: - test_value = str(number_a * number_b) - if test_value == test_value[::-1]: +for letter in text: + if letter.isalpha(): + if letter.isupper(): + result += chr((ord(letter) - 65 + key) % 26 + 65) + else: + result += chr((ord(letter) - 97 + key) % 26 + 97) + else: + result += letter \ No newline at end of file diff --git a/exercises/practice/rotational-cipher/.approaches/config.json b/exercises/practice/rotational-cipher/.approaches/config.json index 3882c1cb97..5cf51697a6 100644 --- a/exercises/practice/rotational-cipher/.approaches/config.json +++ b/exercises/practice/rotational-cipher/.approaches/config.json @@ -13,9 +13,23 @@ }, { "uuid": "6eb99523-b12b-4e72-8ed8-3444b635b9e5", - "slug": "nested-for-loop-optimized", - "title": "Nested for loop optimized", - "blurb": "Nested for loop optimized edition", + "slug": "alphabet", + "title": "Alphabet", + "blurb": "Using the alphabet to rotate the alphabet", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "e539d1a5-f497-402b-a232-7e889f4323c1", + "slug": "str-translate", + "title": "Str Translate", + "blurb": "Using str.translate to rotate the alphabet", + "authors": ["meatball133", "bethanyg"] + }, + { + "uuid": "0c74890e-d96e-47a2-a8bf-93c45dd67f94", + "slug": "recursion", + "title": "Recursion", + "blurb": "Using Recursion to rotate the alphabet", "authors": ["meatball133", "bethanyg"] } ] diff --git a/exercises/practice/rotational-cipher/.approaches/introduction.md b/exercises/practice/rotational-cipher/.approaches/introduction.md index 5c63d42303..90b8d6fa8a 100644 --- a/exercises/practice/rotational-cipher/.approaches/introduction.md +++ b/exercises/practice/rotational-cipher/.approaches/introduction.md @@ -1,12 +1,11 @@ # Introduction -There are various ways to solve `palindrome-products`. -This approaches document shows 2 _common_ strategies, with one being a lot more efficient than the other. -That being said, neither approach here is considered canonical, and other "pythonic" approaches could be added/expanded on in the future. +There are various ways to solve `rotational-cipher`. +You can for example use a [ascii values][ascii], alphabet, recursion, and `str.translate`. ## General guidance -The goal of this exercise is to generate the largest and smallest palindromes from a given range of numbers. +The goal of this exercise is to rotate the letters in a string by a given key. ## Approach: Using ascii values @@ -16,11 +15,11 @@ There the numbers 65-91 in the ascii range represent downcased letters. While 97-123 represent upcased letters. The reason why you might not want to do this approach is that it only supports the english alphabet. -Say we want to use the scandivanian letter: **ä**, then this approach will not work. -Since **ä** has the ascii value of 132. +Say we want to use the scandinavian letter: **å**, then this approach will not work. +Since **å** has the ascii value of 132. ```python -def rotate(text, key) +def rotate(text, key): result = "" for letter in text: if letter.isalpha(): @@ -33,14 +32,16 @@ def rotate(text, key) return result ``` +For more information, check the [ascii values approach][approach-ascii-values]. + ## Approach: Alphabet This approach is similar to the previous one, but instead of using the ascii values, it uses the index of the letter in the alphabet. It requires the storing of a string and unless you are using two strings you have to convert the letters from upper to lower case. -What this approach although give is the posiblity to use any alphabet. -Say we want to use the scandivanian letter: **ä**, then we just add it where we want it: -`abcdefghijklmnopqrstuvwxyzä` and it will rotate correctley around that. +What this approach although give is the possibility to use any alphabet. +Say we want to use the scandinavian letter: **å**, then we just add it where we want it: +`abcdefghijklmnopqrstuvwxyzå` and it will rotate correctly around that. ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz" @@ -58,13 +59,13 @@ def rotate(text, key): return result ``` -For more information, check the [Nested for loop approach][approach-nested-for-loop]. +For more information, check the [Alphabet approach][approach-alphabet]. ## Approach: Str translate This approach is similar to the previous one, but instead of using the index of the letter in the alphabet, it uses the `str.translate` method. -The benefit of this approach is that it has no visable loop, thereby the code becomes more concise. -What to note is that the `str.translate` still loops over the `string` thereby even if it is no visable loop, it doesn't mean that a method is not looping. +The benefit of this approach is that it has no visible loop, thereby the code becomes more concise. +What to note is that the `str.translate` still loops over the `string` thereby even if it is no visible loop, it doesn't mean that a method is not looping. ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz" @@ -74,7 +75,7 @@ def rotate(text, key): return text.translate(str.maketrans(AlPHABET + AlPHABET.upper(), translator + translator.upper())) ``` -You can read more about how to achieve this optimization in: [Nested for loop optimized][approach-nested-for-loop-optimized]. +For more information, check the [Str translate approach][approach-str-translate]. ## Approach: Recursion @@ -100,10 +101,16 @@ def rotate(text, key): return first_letter + rotate(rest, key) ``` +For more information, check the [Recursion approach][approach-recursion]. + ## Benchmark For more information, check the [Performance article][article-performance]. -[approach-nested-for-loop]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop -[approach-nested-for-loop-optimized]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop-optimized -[article-performance]: https://exercism.org/tracks/python/exercises/palindrome-products/articles/performance +[ascii]: https://en.wikipedia.org/wiki/ASCII +[approach-recursion]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/recursion +[approach-str-translate]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/str-translate +[approach-ascii-values]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/ascii-values +[approach-alphabet]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/alphabet +[article-performance]: https://exercism.org/tracks/python/exercises/rotational-cipher/articles/performance +[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit diff --git a/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/content.md b/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/content.md deleted file mode 100644 index 2bc2718391..0000000000 --- a/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/content.md +++ /dev/null @@ -1,92 +0,0 @@ -# Nested For Loop Optimized - -This approach shows that just a few changes can improve the running time of the solution significantly. - -```python -def largest(min_factor, max_factor): - if min_factor > max_factor: - raise ValueError("min must be <= max") - result = 0 - answer = [] - for number_a in range(max_factor, min_factor - 1,-1): - was_bigger = False - for number_b in range(max_factor, number_a - 1, -1): - if number_a * number_b >= result: - was_bigger = True - test_value = str(number_a * number_b) - if test_value == test_value[::-1]: - if number_a * number_b > result: - answer = [] - result = int(test_value) - answer.append([number_a, number_b]) - if not was_bigger: - break - if result == 0: - result = None - return (result, answer) - - -def smallest(min_factor, max_factor): - if min_factor > max_factor: - raise ValueError("min must be <= max") - result = 0 - answer = [] - for number_a in range(min_factor, max_factor+1): - was_smaller = False - for number_b in range(min_factor, max_factor+1): - if number_a * number_b <= result or result == 0: - was_smaller = True - test_value = str(number_a * number_b) - if test_value == test_value[::-1]: - if number_a * number_b < result: - answer = [] - result = int(test_value) - answer.append([number_a, number_b]) - if not was_smaller: - break - if result == 0: - result = None - return (result, answer) -``` - -This approach is very similar to the [nested for loop approach][approach-nested-for-loop], but it has a few optimizations. -To optimize the `largest` function, we have to start the inner loop from the maximum factor and proceed down to the minimum factor. -This allows us to stop the inner loop earlier than before. -We also set the minimum value in the _inner_ loop to the current value of the _outer_ loop. - -Here is an example of how the algorithm works and why the loops need to be modified. -Say we take maximum to be 99 and the minimum 10. -In the first round: - -``` -x = [99 , 98, 97 ...] -y = [99] -``` - -And already we have our result: `9009[91,99]` -Although the loops have to continue to make us sure there are no higher values. - -``` -x = [98, 97, 96 ...] -y = [99, 98] -... -x = [90, 89, 88 ...] -y = [99, 98,97,96,95,94,93,92,91,90] - -Here we can see that the highest value for this "run" is 90 \* 99 = 8910. -Meaning that running beyond this point won't give us any values higher than 9009. - -That is why we introduce the `was_bigger` variable. -With `was_bigger`, we can check if the inner loop has a bigger value than the current result. -If there has not been a bigger value, we can stop the inner loop and stop the outer loop. -We do that by using the [`break`][break] statement. - -If we hadn't modified the direction of the inner loop, it would have started from the minimum factor and gone up to the maximum factor. -This would mean that for every new run in the outer loop, the values would be bigger than the previous run. - -The `smallest` function is optimized in a similar way as `largest` function compared to the original approach. -The only difference is that we have to start the inner loop and outer loop from the minimum factor and go up to the maximum factor. -Since what we want is the smallest value, we have to start from the smallest value and go up to the biggest value. - -[approach-nested-for-loop]: https://exercism.org/tracks/python/exercises/palindrome-products/approaches/nested-for-loop -[break]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops diff --git a/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/snippet.txt b/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/snippet.txt deleted file mode 100644 index bf91dd6753..0000000000 --- a/exercises/practice/rotational-cipher/.approaches/nested-for-loop-optimized/snippet.txt +++ /dev/null @@ -1,5 +0,0 @@ -for number_a in range(max_factor, min_factor - 1,-1): - was_bigger = False - for number_b in range(max_factor, number_a - 1, -1): - if number_a * number_b >= result: - was_bigger = True diff --git a/exercises/practice/rotational-cipher/.approaches/recursion/content.md b/exercises/practice/rotational-cipher/.approaches/recursion/content.md new file mode 100644 index 0000000000..d4e4e01908 --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/recursion/content.md @@ -0,0 +1,42 @@ +# Recursion + +```python +AlPHABET = "abcdefghijklmnopqrstuvwxyz" + +def rotate(text, key): + if text == "": + return "" + first_letter, rest = text[0], text[1:] + if first_letter.isalpha(): + if first_letter.isupper(): + return AlPHABET[(AlPHABET.index(first_letter.lower()) + key) % 26].upper() + rotate(rest, key) + else: + return AlPHABET[(AlPHABET.index(first_letter) + key) % 26] + rotate(rest, key) + else: + return first_letter + rotate(rest, key) +``` + +This approach uses a very similar approach to alphabet. +And uses the same logic but instead of a loop so does this approach use [concept:python/recursion]() to solve the problem. + +Recursion is a programming technique where a function calls itself. +It is a powerful technique, but can be more tricky to implement than a while loop. +Recursion isn't that common in Python, it is more common in functional programming languages, like: [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure]. + +Solving this exercise with recursion removes the need for a "result" variable and the instantiation of a `loop`. +If the number is not equal to one, we call ` + rotate(rest)`. +Then the `rotate` function can execute the same code again with new values. +Meaning we can get a long chain or stack of ` + rotate(rest)` until the rest variable is exhausted and then the code adds `""`. +That translates to something like this: ` + + + + ""`. + +In Python, we can't have a function call itself more than 1000 times by default. +Code that exceeds this recursion limit will throw a [RecursionError][recursion-error]. +While it is possible to adjust the [recursion limit][recursion-limit], doing so risks crashing Python and may also crash your system. +Casually raising the recursion limit is not recommended. + +[clojure]: https://exercism.org/tracks/clojure +[elixir]: https://exercism.org/tracks/elixir +[haskell]: https://exercism.org/tracks/haskell +[recursion]: https://realpython.com/python-thinking-recursively/ +[recursion-error]: https://docs.python.org/3/library/exceptions.html#RecursionError +[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit diff --git a/exercises/practice/rotational-cipher/.approaches/recursion/snippet.txt b/exercises/practice/rotational-cipher/.approaches/recursion/snippet.txt new file mode 100644 index 0000000000..098c419fe7 --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/recursion/snippet.txt @@ -0,0 +1,8 @@ +first_letter, rest = text[0], text[1:] +if first_letter.isalpha(): + if first_letter.isupper(): + return AlPHABET[(AlPHABET.index(first_letter.lower()) + key) % 26].upper() + rotate(rest, key) + else: + return AlPHABET[(AlPHABET.index(first_letter) + key) % 26] + rotate(rest, key) +else: + return first_letter + rotate(rest, key) \ No newline at end of file diff --git a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md new file mode 100644 index 0000000000..e38f8359c7 --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md @@ -0,0 +1,54 @@ +# Str Translate + +```python +AlPHABET = "abcdefghijklmnopqrstuvwxyz" + +def rotate(text, key): + translator = AlPHABET[key:] + AlPHABET[:key] + return text.translate(str.maketrans(AlPHABET + AlPHABET.upper(), translator + translator.upper())) +``` + +This approach uses the [`.translate`][translate] method to solve the problem. +`translate` takes a translation table as an argument. +To create a translation table we use [str.`makestrans`][maketrans]. + +The approach starts with defining the a constant which holds the whole alphabets lowercase letters. +Then the function `rotate` is declared. +Then is the `translator` variable defined with the value of the `AlPHABET` constant [sliced][slicing] from the key to the end and then the `AlPHABET` constant sliced from the start to the key. + +This is done so we have 2 strings which are the same but shifted by the key. +Say we have the `AlPHABET` constant with the value of `abcdefghijklmnopqrstuvwxyz` and the key is 3. +Then the `translator` variable will have the value of `defghijklmnopqrstuvwxyzabc`. + +Then is the `translate` method called on the `text` argument. +The `translate` method takes a translation table as an argument. +To create a translation table we use str.`makestrans`maketrans. + +The `makestrans` method takes 2 arguments. +The first argument is the string which holds the characters which should be translated. +The second argument is the string which holds the characters which the characters from the first argument should be translated to. + +The first argument is the `AlPHABET` constant and the `AlPHABET` constant uppercased. +The second argument is the `translator` variable and the `translator` variable uppercased. + +What the `makestrans` does is that it takes the first argument and maps it to the second argument. +It does that by creating a [dictionary], and converting the letter to [unicode][unicode]. + +```python +>>> str.maketrans("abc", "def") +{97: 100, 98: 101, 99: 102} +``` + +The `translate` method takes the dictionary created by the `makestrans` method and uses it to translate the characters in the `text` argument. + +```python +>>> "abc".translate({97: 100, 98: 101, 99: 102}) +'def' +``` + +When the loop is finished we return the result. + +[maketrans]: https://docs.python.org/3/library/stdtypes.html#str.maketrans +[slicing]: https://www.w3schools.com/python/python_strings_slicing.asp +[translate]: https://docs.python.org/3/library/stdtypes.html#str.translate +[unicode]: https://en.wikipedia.org/wiki/Unicode diff --git a/exercises/practice/rotational-cipher/.approaches/str-translate/snippet.txt b/exercises/practice/rotational-cipher/.approaches/str-translate/snippet.txt new file mode 100644 index 0000000000..75350ae406 --- /dev/null +++ b/exercises/practice/rotational-cipher/.approaches/str-translate/snippet.txt @@ -0,0 +1,5 @@ +AlPHABET = "abcdefghijklmnopqrstuvwxyz" + +def rotate(text, key): + translator = AlPHABET[key:] + AlPHABET[:key] + return text.translate(str.maketrans(AlPHABET + AlPHABET.upper(), translator + translator.upper())) \ No newline at end of file diff --git a/exercises/practice/rotational-cipher/.articles/config.json b/exercises/practice/rotational-cipher/.articles/config.json new file mode 100644 index 0000000000..fe3d6dc2a2 --- /dev/null +++ b/exercises/practice/rotational-cipher/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "8ab0e53c-0e9f-4525-a8ad-dea838e17d8c", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the performance between different approaches", + "authors": ["meatball133", "bethanyg"] + } + ] +} diff --git a/exercises/practice/rotational-cipher/.articles/performance/code/Benchmark.py b/exercises/practice/rotational-cipher/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..2919024a1f --- /dev/null +++ b/exercises/practice/rotational-cipher/.articles/performance/code/Benchmark.py @@ -0,0 +1,90 @@ +import timeit +import sys +import itertools + + +print(sys.version) + + +AlPHABET = "abcdefghijklmnopqrstuvwxyz" +COMBINATIONS = itertools.combinations_with_replacement(f"{AlPHABET[:13]}{AlPHABET[:13].upper()} 12,", 2) +TEST_TEST = "".join([element for sublist in COMBINATIONS for element in sublist]) + +def rotate_ascii(text, key): + result = "" + for letter in text: + if letter.isalpha(): + if letter.isupper(): + result += chr((ord(letter) - 65 + key) % 26 + 65) + else: + result += chr((ord(letter) - 97 + key) % 26 + 97) + else: + result += letter + return result + + +def rotate_alphabet(text, key): + result = "" + for letter in text: + if letter.isalpha(): + if letter.isupper(): + result += AlPHABET[(AlPHABET.index(letter.lower()) + key) % 26].upper() + else: + result += AlPHABET[(AlPHABET.index(letter) + key) % 26] + else: + result += letter + return result + + +def rotate_translate(text, key): + translator = AlPHABET[key:] + AlPHABET[:key] + return text.translate(str.maketrans(AlPHABET + AlPHABET.upper(), translator + translator.upper())) + + +def rotate_recursion(text, key): + if text == "": + return "" + first_letter, rest = text[0], text[1:] + if first_letter.isalpha(): + if first_letter.isupper(): + return AlPHABET[(AlPHABET.index(first_letter.lower()) + key) % 26].upper() + rotate_recursion(rest, key) + else: + return AlPHABET[(AlPHABET.index(first_letter) + key) % 26] + rotate_recursion(rest, key) + else: + return first_letter + rotate_recursion(rest, key) + + + +start_time = timeit.default_timer() +rotate_ascii(TEST_TEST, 25) +print("rotate ascii long :", timeit.default_timer() - start_time) + +start_time = timeit.default_timer() +rotate_alphabet(TEST_TEST, 25) +print("rotate alphabet long :", timeit.default_timer() - start_time) + +start_time = timeit.default_timer() +rotate_translate(TEST_TEST, 25) +print("rotate translate long :", timeit.default_timer() - start_time) + +start_time = timeit.default_timer() +rotate_recursion(TEST_TEST, 25) +print("rotate recursion long :", timeit.default_timer() - start_time) + + + +start_time = timeit.default_timer() +rotate_ascii("abcABC -12", 11) +print("rotate ascii short :", timeit.default_timer() - start_time) + +start_time = timeit.default_timer() +rotate_alphabet("abcABC -12", 11) +print("rotate alphabet short :", timeit.default_timer() - start_time) + +start_time = timeit.default_timer() +rotate_translate("abcABC -12", 11) +print("rotate translate short :", timeit.default_timer() - start_time) + +start_time = timeit.default_timer() +rotate_recursion("abcABC -12", 11) +print("rotate recursion short :", timeit.default_timer() - start_time) diff --git a/exercises/practice/rotational-cipher/.articles/performance/content.md b/exercises/practice/rotational-cipher/.articles/performance/content.md new file mode 100644 index 0000000000..851180f56f --- /dev/null +++ b/exercises/practice/rotational-cipher/.articles/performance/content.md @@ -0,0 +1,43 @@ +# Performance + +In this article, we'll examine the performance difference between approaches for `rotational cipher` in Python. + +The [approaches page][approaches] lists two approaches to this exercise: + +1. [Using recursion][approach-recursion] +2. [Using `str.translate`][approach-str-translate] +3. [Using ascii values][approach-ascii-values] +4. [Using the alphabet][approach-alphabet] + +## Benchmarks + +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. +These tests were run in windows 11, using Python 3.11.1. +Your system results may vary. + +``` +rotate ascii long : 0.000189200000022538 +rotate alphabet long : 0.0002604000037536025 +rotate translate long : 1.3999990187585354e-05 +rotate recursion long : 0.001309900006162934 + +rotate ascii short : 4.999994416721165e-06 +rotate alphabet short : 3.6999990697950125e-06 +rotate translate short : 1.0200004908256233e-05 +rotate recursion short : 5.4000120144337416e-06 +``` + +## Conclusion + +For a long string as input, is the translate approach the fastest, followed by ascii, alphabet and last recursion. +For a short string as input, is the alphabet approach the fastest, followed by ascii, recursion and last translate. + +This means that if you know the input is a short string, the fastest approach is to use the alphabet approach. +On the other hand, if you know the input is a long string, the fastest approach is to use the translate approach. + +[approach-recursion]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/recursion +[approach-str-translate]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/str-translate +[approach-ascii-values]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/ascii-values +[approach-alphabet]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/alphabet +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.articles/performance/code/Benchmark.py +[timeit]: https://docs.python.org/3/library/timeit.html diff --git a/exercises/practice/rotational-cipher/.articles/performance/snippet.md b/exercises/practice/rotational-cipher/.articles/performance/snippet.md new file mode 100644 index 0000000000..0d4f9baba4 --- /dev/null +++ b/exercises/practice/rotational-cipher/.articles/performance/snippet.md @@ -0,0 +1,6 @@ +``` +rotate ascii long : 0.000189200000022538 +rotate alphabet long : 0.0002604000037536025 +rotate translate long : 1.3999990187585354e-05 +rotate recursion long : 0.001309900006162934 +``` From b6185ec43ced29cdabd9bdaa7ee84945c9f6864b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Feb 2023 10:17:58 -0800 Subject: [PATCH 338/826] Changes and Corrections for rotational-cipher Edits, spelling, and grammer nits. --- .../.approaches/alphabet/content.md | 28 ++++---- .../.approaches/ascii-values/content.md | 53 ++++++++------- .../.approaches/introduction.md | 65 +++++++++++++------ .../.approaches/recursion/content.md | 20 +++--- .../.approaches/str-translate/content.md | 31 ++++----- .../.articles/performance/content.md | 12 ++-- 6 files changed, 120 insertions(+), 89 deletions(-) diff --git a/exercises/practice/rotational-cipher/.approaches/alphabet/content.md b/exercises/practice/rotational-cipher/.approaches/alphabet/content.md index 75d85c531e..601a3afc2d 100644 --- a/exercises/practice/rotational-cipher/.approaches/alphabet/content.md +++ b/exercises/practice/rotational-cipher/.approaches/alphabet/content.md @@ -16,28 +16,28 @@ def rotate(text, key): return result ``` -The approach starts with defining the a constant which holds the whole alphabets lowercase letters. -After that the function `rotate` is declared, then a variable `result` is defined with the value of an empty string. +The approach starts with defining the constant `ALPHABET` which is a string of all lowercase letters. +The function `rotate()` is then declared, and a variable `result` is defined as an empty string. -Then is all the letters from the text argument iterated over through a [`for loop`][for-loop]. -Then is checked if the letter is a letter, if it is a letter then is checked if it is a uppercased letter. +The text argument is then iterated over via a [`for loop`][for-loop]. +Each element is checked to make sure it is a letter, and subsequently checked if it is uppercase or lowercase. +Uppercase letters are converted to lowercase. +Then index of each letter is found in the `AlPHABET` constant. +The numeric key value is added to the letter index and [modulo (`%`)][modulo] 26 is used on the result. +Finally, the new number is used as an index into the `AlPHABET` constant, and the resulting letter is converted back to uppercase. -If it is a uppercased letter then it is converted to lowe case and finds its index in the `AlPHABET` constant. -Then is the key added to the index and [modulo (`%`)][modulo] 26 is used on the result. -Then is the letter at the index found in the `AlPHABET` constant and the letter is converted to upcase. +Lowercase letters follow the same process without the conversion steps. -If the letter is a lowercased letter then it does the same process but don't convert the letter to downcase and then to uppercase. +If the element is not a letter (for example, space or punctuation) then it is added directly to the result string. +The result string is returned once the loop finishes. -If the letter is not a letter then is the letter added to the result. -When the loop is finished we return the result. +If only English letters are needed, the constant [`string.ascii_lowercase`][ascii_lowercase] can be imported from the [`string`][string] module. -If you only want to use english letters so could you import the alphabet instead of defining it yourself. -Since in the [`string`][string] module there is a constant called [`ascii_lowercase`][ascii_lowercase] which holds the lowercased alphabet. ```python -import string +from string import ascii_lowercase -AlPHABET = string.ascii_lowercase +AlPHABET = ascii_lowercase ``` [ascii_lowercase]: https://docs.python.org/3/library/string.html#string.ascii_letters diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md index cb637198a9..5b846ac8d4 100644 --- a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md @@ -14,37 +14,44 @@ def rotate(text, key): return result ``` -This approach uses [ascii values][ascii], ascii stands for American Standard Code for Information Interchange. -It is a character encoding standard for electronic communication. -It is a 7-bit code, which means that it can represent 128 different characters. -The system uses numbers to represent various characters, symbols, and other entities. +This approach uses [ascii values][ascii]. +ASCII stands for **A**merican **S**tandard **C**ode for **I**nformation **I**nterchange. +It is a 7-bit character encoding standard for electronic communication first described in 1969, becoming a formal standard in 2015. +It uses numbers to represent 128 different entities including carriage returns, whitespace characters, box characters, alphabetic characters, punctuation, and the numbers 0-9. -In ascii can you find all the downcased letter in the range between 97 and 123. -While the upcased letters are in the range between 65 and 91. +In ascii, all the lowercase English letters appear between 97 and 123. +While the uppercase letters are in the range between 65 and 91. -The reason why you might not want to do this approach is that it only supports the english alphabet. +~~~~exercism/caution -The approach starts with defining the function `rotate`, then a variable `result` is defined with the value of an empty string. -Then is all the letters from the text argument iterated over through a [`for loop`][for-loop]. -Then is checked if the letter is a letter, if it is a letter then is checked if it is a uppercased letter. +This approach only supports the English alphabet. +Non-English alphabets are not contiguous in their ascii number ranges, and are not consistently defined across platforms. +For example, the Scandinavian letter: **å** has the extended ascii value of 132, but is used in combination with Latin characters that appear in the 65-91 and 97-123 ranges. +This means that a shift for an extended ascii word containing **å** won't result in an accurate alphabet position for a Scandinavian language. -Python has a built in function called `ord` that converts a [unicode][unicode] symbol to an integer. -The unicode's first 128 characters are the same as ascii. +~~~~ -If it is a uppercased letter then is [`ord`][ord] used to convert the letters to an integer and is then added with the key and then subtracted with 65. -Then is the result of that [modulo (`%`)][modulo] 26 added with 65. -That is because we want to know which index in the alphabet the letter is. -And if the number is over 26 we want to make sure that it is in the range of 0-26. -So we use modulo to make sure it is in that range. -To use modulo for a range we have to make sure that it starts at zero, thereby are we subtracting the integer value of the letter with 65. -After that to get the back to a letter we add 65 and use the [`chr`][chr] method which converts an an unicode value to a letter. -After that is the new letter added to the result. +The approach starts with defining the function `rotate()`, with a variable `result` is assigned to an empty string. +The elements of the text argument are then iterated over using a [`for loop`][for-loop]. +Each element is checked to see if it is a letter, and then is checked if it is an uppercase letter. -If the letter is a lowercased letter then is the same done but with the ascii value of 97 subtracted with the letter. +Python has a builtin function called `ord` that converts a [unicode][unicode] symbol to an integer representation. +Unicode's first 128 code points have the same numbers as their ascii counterparts. -If the letter is not a letter then is the letter added to the result. -When the loop is finished we return the result. +If the element is an uppercase letter, [`ord`][ord] is used to convert the letter to an integer. +The integer is added to the numeric key and then 65 is subtracted from the total. +Finally, the result is [modulo (`%`)][modulo] 26 (_to put the value within the 2) and 65 is added back. + +This is because we want to know which letter of the alphabet the number will become. +And if the new number is over 26 we want to make sure that it "wraps around" to remain in the range of 0-26. +To properly use modulo for a range we have to make sure that it starts at zero, so we subtract 65. +To get back to a letter in the asciii range we add 65 and use the [`chr`][chr] method to convert the value to a letter. + +The process is the same for a lowercase letter, but with 97 subtracted to put the letter in the lowercase ascii range of 97 - 123. + +Any element that is not a letter (_whitespace or punctuation_) is added directly to the result string. +When all the elements have been looped over, the full result is returned. [ascii]: https://en.wikipedia.org/wiki/ASCII [chr]: https://docs.python.org/3/library/functions.html#chr diff --git a/exercises/practice/rotational-cipher/.approaches/introduction.md b/exercises/practice/rotational-cipher/.approaches/introduction.md index 90b8d6fa8a..4b3e1889bf 100644 --- a/exercises/practice/rotational-cipher/.approaches/introduction.md +++ b/exercises/practice/rotational-cipher/.approaches/introduction.md @@ -1,22 +1,30 @@ # Introduction There are various ways to solve `rotational-cipher`. -You can for example use a [ascii values][ascii], alphabet, recursion, and `str.translate`. +You can for example use [ascii values][ascii], an alphabet `str` or `list`, recursion, or `str.translate`. + ## General guidance -The goal of this exercise is to rotate the letters in a string by a given key. +The goal of this exercise is to shift the letters in a string by a given integer key between 0 and 26. +The letter in the encrypted string is shifted for as many values (or "positions") as the value of the key. ## Approach: Using ascii values -This approach is very simple and easy to understand. -it uses the ascii value of the letters to rotate them. -There the numbers 65-91 in the ascii range represent downcased letters. -While 97-123 represent upcased letters. +This approach is straightforward to understand. +It uses the ascii value of the letters to rotate them within the message. +The numbers 65-91 in the ascii range represent lowercase Latin letters, while 97-123 represent uppercase Latin letters. + + +~~~~exercism/caution + +This approach only supports the English alphabet. +Non-English alphabets are not contiguous in their ascii number ranges, and are not consistently defined across platforms. +For example, the Scandinavian letter: **å** has the extended ascii value of 132, but is used in combination with Latin characters that appear in the 65-91 and 97-123 ranges. +This means that a shift for an extended ascii word containing **å** won't result in an accurate alphabet position for a Scandinavian language. + +~~~~ -The reason why you might not want to do this approach is that it only supports the english alphabet. -Say we want to use the scandinavian letter: **å**, then this approach will not work. -Since **å** has the ascii value of 132. ```python def rotate(text, key): @@ -34,16 +42,20 @@ def rotate(text, key): For more information, check the [ascii values approach][approach-ascii-values]. + ## Approach: Alphabet -This approach is similar to the previous one, but instead of using the ascii values, it uses the index of the letter in the alphabet. -It requires the storing of a string and unless you are using two strings you have to convert the letters from upper to lower case. +This approach is similar to the ascii one, but it uses the index number of each letter in an alphabet string. +It requires making a string for all the letters in an alphabet. +And unless two strings are used, you will have to convert individual letters from lower to upper case (or vice-versa). + +The big advantage of this approach is the ability to use any alphabet (_although there are some issues with combining characters in Unicode._). +Here, if we want to use the scandinavian letter: **å**, we can simply insert it into our string where we want it: +`abcdefghijklmnopqrstuvwxyzå` and the rotation will work correctly. -What this approach although give is the possibility to use any alphabet. -Say we want to use the scandinavian letter: **å**, then we just add it where we want it: -`abcdefghijklmnopqrstuvwxyzå` and it will rotate correctly around that. ```python +# This only uses English characters AlPHABET = "abcdefghijklmnopqrstuvwxyz" def rotate(text, key): @@ -61,14 +73,19 @@ def rotate(text, key): For more information, check the [Alphabet approach][approach-alphabet]. + ## Approach: Str translate -This approach is similar to the previous one, but instead of using the index of the letter in the alphabet, it uses the `str.translate` method. -The benefit of this approach is that it has no visible loop, thereby the code becomes more concise. -What to note is that the `str.translate` still loops over the `string` thereby even if it is no visible loop, it doesn't mean that a method is not looping. +This approach uses the [`str.translate`][str-translate] method to create a mapping from input to shifted string instead of using the index of an alphabet string to calculate the shift. +The benefit of this approach is that it has no visible loop, making the code more concise. + +~~~~exercism/note +`str.translate` **still loops over the `string`** even if it is not visibly doing so. +~~~~ + ```python -AlPHABET = "abcdefghijklmnopqrstuvwxyz" +AlPHABET = "abcdefghijklmnopqrstuvwxyz def rotate(text, key): translator = AlPHABET[key:] + AlPHABET[:key] @@ -77,13 +94,19 @@ def rotate(text, key): For more information, check the [Str translate approach][approach-str-translate]. + ## Approach: Recursion -In this approach we use a recursive function. +This approach uses a recursive function. A recursive function is a function that calls itself. This approach can be more concise than other approaches, and may also be more readable for some audiences. -The reason why you might not want to use this approach is that Python has a [recursion limit][recursion-limit] with a default of 1000. + +~~~~exercism/caution +Python does not have any tail-call optimization and has a default [recursion limit][recursion-limit] of 1000 calls on the stack. +Calculate your base case carefully to avoid errors. +~~~~ + ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz" @@ -103,6 +126,7 @@ def rotate(text, key): For more information, check the [Recursion approach][approach-recursion]. + ## Benchmark For more information, check the [Performance article][article-performance]. @@ -114,3 +138,4 @@ For more information, check the [Performance article][article-performance]. [approach-alphabet]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/alphabet [article-performance]: https://exercism.org/tracks/python/exercises/rotational-cipher/articles/performance [recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit +[str-translate]: https://docs.python.org/3/library/stdtypes.html?highlight=str%20translate#str.translate diff --git a/exercises/practice/rotational-cipher/.approaches/recursion/content.md b/exercises/practice/rotational-cipher/.approaches/recursion/content.md index d4e4e01908..ca7bf2393d 100644 --- a/exercises/practice/rotational-cipher/.approaches/recursion/content.md +++ b/exercises/practice/rotational-cipher/.approaches/recursion/content.md @@ -16,23 +16,25 @@ def rotate(text, key): return first_letter + rotate(rest, key) ``` -This approach uses a very similar approach to alphabet. -And uses the same logic but instead of a loop so does this approach use [concept:python/recursion]() to solve the problem. +This approach uses the same logic as that of the `alphabet` approach, but instead of a `loop`, [concept:python/recursion]() is used to parse the text and solve the problem. Recursion is a programming technique where a function calls itself. -It is a powerful technique, but can be more tricky to implement than a while loop. -Recursion isn't that common in Python, it is more common in functional programming languages, like: [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure]. +It is powerful, but can be more tricky to implement than a `while loop` or `for loop`. +Recursive techniques are more common in functional programming languages like [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure] than they are in Python. -Solving this exercise with recursion removes the need for a "result" variable and the instantiation of a `loop`. +Solving this exercise with recursion removes the need for a `result` variable and the instantiation of a `loop`. If the number is not equal to one, we call ` + rotate(rest)`. Then the `rotate` function can execute the same code again with new values. -Meaning we can get a long chain or stack of ` + rotate(rest)` until the rest variable is exhausted and then the code adds `""`. +We can build a long chain or "stack" of ` + rotate(rest)` calls until the `rest` variable is exhausted and the code adds `""`. That translates to something like this: ` + + + + ""`. -In Python, we can't have a function call itself more than 1000 times by default. -Code that exceeds this recursion limit will throw a [RecursionError][recursion-error]. -While it is possible to adjust the [recursion limit][recursion-limit], doing so risks crashing Python and may also crash your system. + +~~~~exercism/note +By default, we can't have a function call itself more than 1000 times. +Code that exceeds this recursion limit will throw a RecursionError. +While it is possible to adjust the recursion limit, doing so risks crashing Python and may also crash your system. Casually raising the recursion limit is not recommended. +~~~~ [clojure]: https://exercism.org/tracks/clojure [elixir]: https://exercism.org/tracks/elixir diff --git a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md index e38f8359c7..b95f5139b1 100644 --- a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md +++ b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md @@ -8,45 +8,42 @@ def rotate(text, key): return text.translate(str.maketrans(AlPHABET + AlPHABET.upper(), translator + translator.upper())) ``` -This approach uses the [`.translate`][translate] method to solve the problem. +This approach uses the [`.translate`][translate] method. `translate` takes a translation table as an argument. To create a translation table we use [str.`makestrans`][maketrans]. -The approach starts with defining the a constant which holds the whole alphabets lowercase letters. -Then the function `rotate` is declared. -Then is the `translator` variable defined with the value of the `AlPHABET` constant [sliced][slicing] from the key to the end and then the `AlPHABET` constant sliced from the start to the key. +This approach starts with defining a constant of all the lowercase letters in the alphabet. +Then the function `rotate()` is declared. +A `translator` variable defined with the value of the `AlPHABET` constant [sliced][slicing] from the key to the end and then sliced from the start to the key. -This is done so we have 2 strings which are the same but shifted by the key. +This is done so we have 2 strings which are the same but shifted by the key value. Say we have the `AlPHABET` constant with the value of `abcdefghijklmnopqrstuvwxyz` and the key is 3. Then the `translator` variable will have the value of `defghijklmnopqrstuvwxyzabc`. -Then is the `translate` method called on the `text` argument. -The `translate` method takes a translation table as an argument. -To create a translation table we use str.`makestrans`maketrans. +`str.translate` is then called on the `text` argument. +`str.translate` takes a translation table mapping start values to transformed values as an argument. +To create a translation table, `str.makestrans` is used. +`makestrans` takes 2 arguments: the first is the string to be translated, and the second is the string the first argument should be translated to. -The `makestrans` method takes 2 arguments. -The first argument is the string which holds the characters which should be translated. -The second argument is the string which holds the characters which the characters from the first argument should be translated to. +For our solution, the first argument is the `AlPHABET` constant + the `AlPHABET` constant in uppercase. +The second argument is the `translator` variable + uppercase `translator` variable. -The first argument is the `AlPHABET` constant and the `AlPHABET` constant uppercased. -The second argument is the `translator` variable and the `translator` variable uppercased. +`makestrans` does is that it takes the [Unicode][unicode] values of the first argument and maps them to the corresponding Unicode values in the second argument, creating a `dict`. -What the `makestrans` does is that it takes the first argument and maps it to the second argument. -It does that by creating a [dictionary], and converting the letter to [unicode][unicode]. ```python >>> str.maketrans("abc", "def") {97: 100, 98: 101, 99: 102} ``` -The `translate` method takes the dictionary created by the `makestrans` method and uses it to translate the characters in the `text` argument. +`str.translate` takes the `dict` created by `str.makestrans` and uses it to translate the characters in the `text` argument. ```python >>> "abc".translate({97: 100, 98: 101, 99: 102}) 'def' ``` -When the loop is finished we return the result. +Once the `str.translate` loop completes, we return the `result`. [maketrans]: https://docs.python.org/3/library/stdtypes.html#str.maketrans [slicing]: https://www.w3schools.com/python/python_strings_slicing.asp diff --git a/exercises/practice/rotational-cipher/.articles/performance/content.md b/exercises/practice/rotational-cipher/.articles/performance/content.md index 851180f56f..9833401155 100644 --- a/exercises/practice/rotational-cipher/.articles/performance/content.md +++ b/exercises/practice/rotational-cipher/.articles/performance/content.md @@ -2,7 +2,7 @@ In this article, we'll examine the performance difference between approaches for `rotational cipher` in Python. -The [approaches page][approaches] lists two approaches to this exercise: +The [approaches page][approaches] lists four approaches to this exercise: 1. [Using recursion][approach-recursion] 2. [Using `str.translate`][approach-str-translate] @@ -12,7 +12,7 @@ The [approaches page][approaches] lists two approaches to this exercise: ## Benchmarks To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. -These tests were run in windows 11, using Python 3.11.1. +These tests were run in `Windows 11`, using Python `3.11.1`. Your system results may vary. ``` @@ -29,11 +29,11 @@ rotate recursion short : 5.4000120144337416e-06 ## Conclusion -For a long string as input, is the translate approach the fastest, followed by ascii, alphabet and last recursion. -For a short string as input, is the alphabet approach the fastest, followed by ascii, recursion and last translate. +For a long string as input, the `str.translate` approach the fastest, followed by ascii, alphabet, and finally recursion. +For a short string as input, is the alphabet approach the is the fastest, followed by ascii, recursion and finally `str.translate`. -This means that if you know the input is a short string, the fastest approach is to use the alphabet approach. -On the other hand, if you know the input is a long string, the fastest approach is to use the translate approach. +This means that if you know the input is a short string, the fastest approach is to use the alphabet, and forgo the overhead of making and saving a translation dictionary. +On the other hand, if the input is a long string, the overhead of making a dictionary is amortized over the length of the text to be translated, and the fastest approach becomes `str.translate`. [approach-recursion]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/recursion [approach-str-translate]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/str-translate From 2f190cd045a8a7023c93cb1e4a95e005a955c04e Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 6 Feb 2023 19:36:14 +0100 Subject: [PATCH 339/826] Updated formating --- .../.approaches/alphabet/content.md | 1 - .../.approaches/ascii-values/content.md | 7 +++--- .../.approaches/introduction.md | 23 +++++-------------- .../.approaches/recursion/content.md | 5 ++-- .../.approaches/str-translate/content.md | 3 +-- .../.articles/performance/content.md | 2 +- 6 files changed, 13 insertions(+), 28 deletions(-) diff --git a/exercises/practice/rotational-cipher/.approaches/alphabet/content.md b/exercises/practice/rotational-cipher/.approaches/alphabet/content.md index 601a3afc2d..e79625dbdf 100644 --- a/exercises/practice/rotational-cipher/.approaches/alphabet/content.md +++ b/exercises/practice/rotational-cipher/.approaches/alphabet/content.md @@ -33,7 +33,6 @@ The result string is returned once the loop finishes. If only English letters are needed, the constant [`string.ascii_lowercase`][ascii_lowercase] can be imported from the [`string`][string] module. - ```python from string import ascii_lowercase diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md index 5b846ac8d4..0b9fb07718 100644 --- a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md @@ -22,15 +22,14 @@ It uses numbers to represent 128 different entities including carriage returns, In ascii, all the lowercase English letters appear between 97 and 123. While the uppercase letters are in the range between 65 and 91. -~~~~exercism/caution +```exercism/caution This approach only supports the English alphabet. Non-English alphabets are not contiguous in their ascii number ranges, and are not consistently defined across platforms. For example, the Scandinavian letter: **å** has the extended ascii value of 132, but is used in combination with Latin characters that appear in the 65-91 and 97-123 ranges. This means that a shift for an extended ascii word containing **å** won't result in an accurate alphabet position for a Scandinavian language. -~~~~ - +``` The approach starts with defining the function `rotate()`, with a variable `result` is assigned to an empty string. The elements of the text argument are then iterated over using a [`for loop`][for-loop]. @@ -41,7 +40,7 @@ Unicode's first 128 code points have the same numbers as their ascii counterpart If the element is an uppercase letter, [`ord`][ord] is used to convert the letter to an integer. The integer is added to the numeric key and then 65 is subtracted from the total. -Finally, the result is [modulo (`%`)][modulo] 26 (_to put the value within the 2) and 65 is added back. +Finally, the result is [modulo (`%`)][modulo] 26 (_to put the value within the 2_) and 65 is added back. This is because we want to know which letter of the alphabet the number will become. And if the new number is over 26 we want to make sure that it "wraps around" to remain in the range of 0-26. diff --git a/exercises/practice/rotational-cipher/.approaches/introduction.md b/exercises/practice/rotational-cipher/.approaches/introduction.md index 4b3e1889bf..67a6264dfd 100644 --- a/exercises/practice/rotational-cipher/.approaches/introduction.md +++ b/exercises/practice/rotational-cipher/.approaches/introduction.md @@ -3,7 +3,6 @@ There are various ways to solve `rotational-cipher`. You can for example use [ascii values][ascii], an alphabet `str` or `list`, recursion, or `str.translate`. - ## General guidance The goal of this exercise is to shift the letters in a string by a given integer key between 0 and 26. @@ -15,16 +14,14 @@ This approach is straightforward to understand. It uses the ascii value of the letters to rotate them within the message. The numbers 65-91 in the ascii range represent lowercase Latin letters, while 97-123 represent uppercase Latin letters. - -~~~~exercism/caution +```exercism/caution This approach only supports the English alphabet. Non-English alphabets are not contiguous in their ascii number ranges, and are not consistently defined across platforms. For example, the Scandinavian letter: **å** has the extended ascii value of 132, but is used in combination with Latin characters that appear in the 65-91 and 97-123 ranges. This means that a shift for an extended ascii word containing **å** won't result in an accurate alphabet position for a Scandinavian language. -~~~~ - +``` ```python def rotate(text, key): @@ -42,7 +39,6 @@ def rotate(text, key): For more information, check the [ascii values approach][approach-ascii-values]. - ## Approach: Alphabet This approach is similar to the ascii one, but it uses the index number of each letter in an alphabet string. @@ -53,7 +49,6 @@ The big advantage of this approach is the ability to use any alphabet (_although Here, if we want to use the scandinavian letter: **å**, we can simply insert it into our string where we want it: `abcdefghijklmnopqrstuvwxyzå` and the rotation will work correctly. - ```python # This only uses English characters AlPHABET = "abcdefghijklmnopqrstuvwxyz" @@ -73,16 +68,14 @@ def rotate(text, key): For more information, check the [Alphabet approach][approach-alphabet]. - ## Approach: Str translate This approach uses the [`str.translate`][str-translate] method to create a mapping from input to shifted string instead of using the index of an alphabet string to calculate the shift. The benefit of this approach is that it has no visible loop, making the code more concise. -~~~~exercism/note +```exercism/note `str.translate` **still loops over the `string`** even if it is not visibly doing so. -~~~~ - +``` ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz @@ -94,19 +87,16 @@ def rotate(text, key): For more information, check the [Str translate approach][approach-str-translate]. - ## Approach: Recursion This approach uses a recursive function. A recursive function is a function that calls itself. This approach can be more concise than other approaches, and may also be more readable for some audiences. - -~~~~exercism/caution +```exercism/caution Python does not have any tail-call optimization and has a default [recursion limit][recursion-limit] of 1000 calls on the stack. Calculate your base case carefully to avoid errors. -~~~~ - +``` ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz" @@ -126,7 +116,6 @@ def rotate(text, key): For more information, check the [Recursion approach][approach-recursion]. - ## Benchmark For more information, check the [Performance article][article-performance]. diff --git a/exercises/practice/rotational-cipher/.approaches/recursion/content.md b/exercises/practice/rotational-cipher/.approaches/recursion/content.md index ca7bf2393d..1addd0849f 100644 --- a/exercises/practice/rotational-cipher/.approaches/recursion/content.md +++ b/exercises/practice/rotational-cipher/.approaches/recursion/content.md @@ -28,13 +28,12 @@ Then the `rotate` function can execute the same code again with new values. We can build a long chain or "stack" of ` + rotate(rest)` calls until the `rest` variable is exhausted and the code adds `""`. That translates to something like this: ` + + + + ""`. - -~~~~exercism/note +```exercism/note By default, we can't have a function call itself more than 1000 times. Code that exceeds this recursion limit will throw a RecursionError. While it is possible to adjust the recursion limit, doing so risks crashing Python and may also crash your system. Casually raising the recursion limit is not recommended. -~~~~ +``` [clojure]: https://exercism.org/tracks/clojure [elixir]: https://exercism.org/tracks/elixir diff --git a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md index b95f5139b1..bee4885a7b 100644 --- a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md +++ b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md @@ -30,13 +30,12 @@ The second argument is the `translator` variable + uppercase `translator` variab `makestrans` does is that it takes the [Unicode][unicode] values of the first argument and maps them to the corresponding Unicode values in the second argument, creating a `dict`. - ```python >>> str.maketrans("abc", "def") {97: 100, 98: 101, 99: 102} ``` -`str.translate` takes the `dict` created by `str.makestrans` and uses it to translate the characters in the `text` argument. +`str.translate` takes the `dict` created by `str.makestrans` and uses it to translate the characters in the `text` argument. ```python >>> "abc".translate({97: 100, 98: 101, 99: 102}) diff --git a/exercises/practice/rotational-cipher/.articles/performance/content.md b/exercises/practice/rotational-cipher/.articles/performance/content.md index 9833401155..92d6c1f14d 100644 --- a/exercises/practice/rotational-cipher/.articles/performance/content.md +++ b/exercises/practice/rotational-cipher/.articles/performance/content.md @@ -30,7 +30,7 @@ rotate recursion short : 5.4000120144337416e-06 ## Conclusion For a long string as input, the `str.translate` approach the fastest, followed by ascii, alphabet, and finally recursion. -For a short string as input, is the alphabet approach the is the fastest, followed by ascii, recursion and finally `str.translate`. +For a short string as input, is the alphabet approach the is the fastest, followed by ascii, recursion and finally `str.translate`. This means that if you know the input is a short string, the fastest approach is to use the alphabet, and forgo the overhead of making and saving a translation dictionary. On the other hand, if the input is a long string, the overhead of making a dictionary is amortized over the length of the text to be translated, and the fastest approach becomes `str.translate`. From 3d714863bf0deb53d18885b9b84eecd14895d37c Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 6 Feb 2023 19:45:50 +0100 Subject: [PATCH 340/826] Added back tilda --- .../.approaches/ascii-values/content.md | 6 +++--- .../rotational-cipher/.approaches/introduction.md | 12 ++++++------ .../.approaches/recursion/content.md | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md index 0b9fb07718..5b37134ed0 100644 --- a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md @@ -22,14 +22,14 @@ It uses numbers to represent 128 different entities including carriage returns, In ascii, all the lowercase English letters appear between 97 and 123. While the uppercase letters are in the range between 65 and 91. -```exercism/caution +~~~~exercism/caution This approach only supports the English alphabet. Non-English alphabets are not contiguous in their ascii number ranges, and are not consistently defined across platforms. For example, the Scandinavian letter: **å** has the extended ascii value of 132, but is used in combination with Latin characters that appear in the 65-91 and 97-123 ranges. This means that a shift for an extended ascii word containing **å** won't result in an accurate alphabet position for a Scandinavian language. -``` +~~~~ The approach starts with defining the function `rotate()`, with a variable `result` is assigned to an empty string. The elements of the text argument are then iterated over using a [`for loop`][for-loop]. @@ -40,7 +40,7 @@ Unicode's first 128 code points have the same numbers as their ascii counterpart If the element is an uppercase letter, [`ord`][ord] is used to convert the letter to an integer. The integer is added to the numeric key and then 65 is subtracted from the total. -Finally, the result is [modulo (`%`)][modulo] 26 (_to put the value within the 2_) and 65 is added back. +Finally, the result is [modulo (%)][modulo] 26 (_to put the value within the 0-26 range_) and 65 is added back. This is because we want to know which letter of the alphabet the number will become. And if the new number is over 26 we want to make sure that it "wraps around" to remain in the range of 0-26. diff --git a/exercises/practice/rotational-cipher/.approaches/introduction.md b/exercises/practice/rotational-cipher/.approaches/introduction.md index 67a6264dfd..f8c5e95ed0 100644 --- a/exercises/practice/rotational-cipher/.approaches/introduction.md +++ b/exercises/practice/rotational-cipher/.approaches/introduction.md @@ -14,14 +14,14 @@ This approach is straightforward to understand. It uses the ascii value of the letters to rotate them within the message. The numbers 65-91 in the ascii range represent lowercase Latin letters, while 97-123 represent uppercase Latin letters. -```exercism/caution +~~~~exercism/caution This approach only supports the English alphabet. Non-English alphabets are not contiguous in their ascii number ranges, and are not consistently defined across platforms. For example, the Scandinavian letter: **å** has the extended ascii value of 132, but is used in combination with Latin characters that appear in the 65-91 and 97-123 ranges. This means that a shift for an extended ascii word containing **å** won't result in an accurate alphabet position for a Scandinavian language. -``` +~~~~ ```python def rotate(text, key): @@ -73,9 +73,9 @@ For more information, check the [Alphabet approach][approach-alphabet]. This approach uses the [`str.translate`][str-translate] method to create a mapping from input to shifted string instead of using the index of an alphabet string to calculate the shift. The benefit of this approach is that it has no visible loop, making the code more concise. -```exercism/note +~~~~exercism/note `str.translate` **still loops over the `string`** even if it is not visibly doing so. -``` +~~~~ ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz @@ -93,10 +93,10 @@ This approach uses a recursive function. A recursive function is a function that calls itself. This approach can be more concise than other approaches, and may also be more readable for some audiences. -```exercism/caution +~~~~exercism/caution Python does not have any tail-call optimization and has a default [recursion limit][recursion-limit] of 1000 calls on the stack. Calculate your base case carefully to avoid errors. -``` +~~~~ ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz" diff --git a/exercises/practice/rotational-cipher/.approaches/recursion/content.md b/exercises/practice/rotational-cipher/.approaches/recursion/content.md index 1addd0849f..689f8aaed7 100644 --- a/exercises/practice/rotational-cipher/.approaches/recursion/content.md +++ b/exercises/practice/rotational-cipher/.approaches/recursion/content.md @@ -28,12 +28,12 @@ Then the `rotate` function can execute the same code again with new values. We can build a long chain or "stack" of ` + rotate(rest)` calls until the `rest` variable is exhausted and the code adds `""`. That translates to something like this: ` + + + + ""`. -```exercism/note +~~~~exercism/note By default, we can't have a function call itself more than 1000 times. Code that exceeds this recursion limit will throw a RecursionError. While it is possible to adjust the recursion limit, doing so risks crashing Python and may also crash your system. Casually raising the recursion limit is not recommended. -``` +~~~~ [clojure]: https://exercism.org/tracks/clojure [elixir]: https://exercism.org/tracks/elixir From 3c2f97b27cd43e99cd082ec164cebeaa992375cf Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 6 Feb 2023 19:51:32 +0100 Subject: [PATCH 341/826] fix --- .../practice/rotational-cipher/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/rotational-cipher/.articles/performance/content.md b/exercises/practice/rotational-cipher/.articles/performance/content.md index 92d6c1f14d..0b40eb7cde 100644 --- a/exercises/practice/rotational-cipher/.articles/performance/content.md +++ b/exercises/practice/rotational-cipher/.articles/performance/content.md @@ -30,7 +30,7 @@ rotate recursion short : 5.4000120144337416e-06 ## Conclusion For a long string as input, the `str.translate` approach the fastest, followed by ascii, alphabet, and finally recursion. -For a short string as input, is the alphabet approach the is the fastest, followed by ascii, recursion and finally `str.translate`. +For a short string as input, is the alphabet approach the fastest, followed by ascii, recursion and finally `str.translate`. This means that if you know the input is a short string, the fastest approach is to use the alphabet, and forgo the overhead of making and saving a translation dictionary. On the other hand, if the input is a long string, the overhead of making a dictionary is amortized over the length of the text to be translated, and the fastest approach becomes `str.translate`. From c63546060c307e160a8a0d9b2e6a3643bc6e368d Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 6 Feb 2023 19:56:53 +0100 Subject: [PATCH 342/826] fix --- .../rotational-cipher/.approaches/ascii-values/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md index 5b37134ed0..a126898526 100644 --- a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md @@ -45,7 +45,7 @@ Finally, the result is [modulo (%)][modulo] 26 (_to put the value within the 0-2 This is because we want to know which letter of the alphabet the number will become. And if the new number is over 26 we want to make sure that it "wraps around" to remain in the range of 0-26. To properly use modulo for a range we have to make sure that it starts at zero, so we subtract 65. -To get back to a letter in the asciii range we add 65 and use the [`chr`][chr] method to convert the value to a letter. +To get back to a letter in the ascii range we add 65 and use the [`chr`][chr] method to convert the value to a letter. The process is the same for a lowercase letter, but with 97 subtracted to put the letter in the lowercase ascii range of 97 - 123. From 588058183e1c976982e458344351fcf7d5c14ded Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Feb 2023 11:02:42 -0800 Subject: [PATCH 343/826] Apply suggestions from code review Two final nits... --- .../rotational-cipher/.approaches/ascii-values/content.md | 2 +- .../rotational-cipher/.approaches/str-translate/content.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md index a126898526..c3fba5945a 100644 --- a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md @@ -47,7 +47,7 @@ And if the new number is over 26 we want to make sure that it "wraps around" to To properly use modulo for a range we have to make sure that it starts at zero, so we subtract 65. To get back to a letter in the ascii range we add 65 and use the [`chr`][chr] method to convert the value to a letter. -The process is the same for a lowercase letter, but with 97 subtracted to put the letter in the lowercase ascii range of 97 - 123. +The process is the same for a lowercase letter, but with 97 subtracted/added to put the letter in the lowercase ascii range of 97 - 123. Any element that is not a letter (_whitespace or punctuation_) is added directly to the result string. When all the elements have been looped over, the full result is returned. diff --git a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md index bee4885a7b..b80da8d2ec 100644 --- a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md +++ b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md @@ -28,7 +28,7 @@ To create a translation table, `str.makestrans` is used. For our solution, the first argument is the `AlPHABET` constant + the `AlPHABET` constant in uppercase. The second argument is the `translator` variable + uppercase `translator` variable. -`makestrans` does is that it takes the [Unicode][unicode] values of the first argument and maps them to the corresponding Unicode values in the second argument, creating a `dict`. +`str.makestrans` takes the [Unicode][unicode] values of the first argument and maps them to the corresponding Unicode values in the second argument, creating a `dict`. ```python >>> str.maketrans("abc", "def") From c8967079f25f9864d9eef143a77554180c1322be Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Feb 2023 11:04:42 -0800 Subject: [PATCH 344/826] Update exercises/practice/rotational-cipher/.articles/performance/content.md --- .../practice/rotational-cipher/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/rotational-cipher/.articles/performance/content.md b/exercises/practice/rotational-cipher/.articles/performance/content.md index 0b40eb7cde..8401b40e25 100644 --- a/exercises/practice/rotational-cipher/.articles/performance/content.md +++ b/exercises/practice/rotational-cipher/.articles/performance/content.md @@ -29,7 +29,7 @@ rotate recursion short : 5.4000120144337416e-06 ## Conclusion -For a long string as input, the `str.translate` approach the fastest, followed by ascii, alphabet, and finally recursion. +For a long string as input, the `str.translate` approach is the fastest, followed by ascii, alphabet, and finally recursion. For a short string as input, is the alphabet approach the fastest, followed by ascii, recursion and finally `str.translate`. This means that if you know the input is a short string, the fastest approach is to use the alphabet, and forgo the overhead of making and saving a translation dictionary. From 5e8951a574473436bad39e9f10ebf6ee9fe45b2d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Feb 2023 16:41:40 -0800 Subject: [PATCH 345/826] Addressed issues 3181 and 3161 for lasagna test file. --- .../guidos-gorgeous-lasagna/lasagna_test.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py index 3a10c9db6b..aeacc53b07 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py @@ -1,29 +1,32 @@ import unittest import pytest - +# For this first exercise, it is really important to be clear about how we are importing names for tests. +# To that end, we are putting a try/catch around imports and throwing specific messages to help students +# decode that they need to create and title their constants and functions in a specific way. try: from lasagna import (EXPECTED_BAKE_TIME, bake_time_remaining, preparation_time_in_minutes, elapsed_time_in_minutes) - +# Here, we are separating the constant import errors from the function name import errors except ImportError as import_fail: message = import_fail.args[0].split('(', maxsplit=1) item_name = import_fail.args[0].split()[3] - if 'EXPECTED_BAKE_TIME' in message: + if 'EXPECTED_BAKE_TIME' in item_name: # pylint: disable=raise-missing-from - raise ImportError(f'We can not find or import the constant {item_name} in your' - " 'lasagna.py' file. Did you mis-name or forget to define it?") + raise ImportError(f'\n\nMISSING CONSTANT --> \nWe can not find or import the constant {item_name} in your' + " 'lasagna.py' file.\nDid you mis-name or forget to define it?") from None else: item_name = item_name[:-1] + "()'" # pylint: disable=raise-missing-from - raise ImportError("In your 'lasagna.py' file, we can not find or import the" - f' function named {item_name}. Did you mis-name or forget to define it?') + raise ImportError("\n\nMISSING FUNCTION --> In your 'lasagna.py' file, we can not find or import the" + f' function named {item_name}. \nDid you mis-name or forget to define it?') from None +# Here begins the formal test cases for the exercise. class LasagnaTest(unittest.TestCase): @pytest.mark.task(taskno=1) @@ -64,9 +67,16 @@ def test_elapsed_time_in_minutes(self): @pytest.mark.task(taskno=5) def test_docstrings_were_written(self): + """Validate function.__doc__ exists for each function. + Check the attribute dictionary of each listed function + for the presence of a __doc__ key. + + :return: unexpectedly None error when __doc__ key is missing. + """ functions = [bake_time_remaining, preparation_time_in_minutes, elapsed_time_in_minutes] for variant, function in enumerate(functions, start=1): with self.subTest(f'variation #{variant}', function=function): failure_msg = f'Expected a docstring for `{function.__name__}`, but received `None` instead.' + # Check that the __doc__ key is populated for the function. self.assertIsNotNone(function.__doc__, msg=failure_msg) From ba8dbecf2acdb42cc8697443a1ce3bf499bc2a4d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 22 Feb 2023 18:41:49 -0800 Subject: [PATCH 346/826] Apply suggestions from code review Co-authored-by: Victor Goff --- exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py index aeacc53b07..d200cb926a 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py @@ -18,7 +18,7 @@ if 'EXPECTED_BAKE_TIME' in item_name: # pylint: disable=raise-missing-from raise ImportError(f'\n\nMISSING CONSTANT --> \nWe can not find or import the constant {item_name} in your' - " 'lasagna.py' file.\nDid you mis-name or forget to define it?") from None + " 'lasagna.py' file.\nDid you misname or forget to define it?") from None else: item_name = item_name[:-1] + "()'" # pylint: disable=raise-missing-from From 27fa70430bc15ae8966b3d6cc48a7c4eee65e771 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 22 Feb 2023 18:42:16 -0800 Subject: [PATCH 347/826] Apply suggestions from code review Co-authored-by: Victor Goff --- exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py index d200cb926a..7d0a7d9f1b 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py @@ -23,7 +23,7 @@ item_name = item_name[:-1] + "()'" # pylint: disable=raise-missing-from raise ImportError("\n\nMISSING FUNCTION --> In your 'lasagna.py' file, we can not find or import the" - f' function named {item_name}. \nDid you mis-name or forget to define it?') from None + f' function named {item_name}. \nDid you misname or forget to define it?') from None # Here begins the formal test cases for the exercise. From 2f73fb34ce49d2e1814c134fb41ec395c9ca001d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 18 Feb 2023 01:05:28 -0800 Subject: [PATCH 348/826] Additional edits and adjustments to introduction and related files for clarity. --- .../guidos-gorgeous-lasagna/.docs/hints.md | 15 +- .../.docs/instructions.md | 2 +- .../.docs/introduction.md | 189 +++++++++++------- .../guidos-gorgeous-lasagna/lasagna.py | 46 ++++- 4 files changed, 165 insertions(+), 87 deletions(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md index 41e3c669f0..351daef986 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md @@ -4,6 +4,7 @@ - [The Python Tutorial][the python tutorial] can be a great introduction. - [Numbers][numbers] in Python can be integers, floats, or complex. +- [PEP 8][PEP8] is the Python code style guide. ## 1. Define expected bake time in minutes @@ -33,13 +34,13 @@ - Clearly [commenting][comments] and [documenting][docstrings] your code according to [PEP257][PEP257] is always recommended. -[the python tutorial]: https://docs.python.org/3/tutorial/introduction.html -[numbers]: https://docs.python.org/3/tutorial/introduction.html#numbers -[naming]: https://realpython.com/python-variables/ +[PEP257]: https://www.python.org/dev/peps/pep-0257/ [assignment]: https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment-stmt -[defining functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions -[return]: https://docs.python.org/3/reference/simple_stmts.html#return -[python as a calculator]: https://docs.python.org/3/tutorial/introduction.html#using-python-as-a-calculator [comments]: https://realpython.com/python-comments-guide/ +[defining functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [docstrings]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings -[PEP257]: https://www.python.org/dev/peps/pep-0257/ \ No newline at end of file +[naming]: https://realpython.com/python-variables/ +[numbers]: https://docs.python.org/3/tutorial/introduction.html#numbers +[python as a calculator]: https://docs.python.org/3/tutorial/introduction.html#using-python-as-a-calculator +[return]: https://docs.python.org/3/reference/simple_stmts.html#return +[the python tutorial]: https://docs.python.org/3/tutorial/introduction.html diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md index 321e77761b..f4b9480e50 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md @@ -49,7 +49,7 @@ This function should return the total number of minutes you've been cooking, or ## 5. Update the recipe with notes -Go back through the recipe, adding notes and documentation. +Go back through the recipe, adding notes in the form of function docstrings. ```python def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index 1703d43082..e9d5b96db6 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -1,29 +1,59 @@ # Introduction -[Python][python docs] is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. +Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. -Programming across paradigms is fully _supported_ -- but internally, [everything in Python is an object][everythings an object]. +Python supports Imperative, declarative (e.g. functional), and object oriented programming _styles_, but internally [everything in Python is an object][everythings an object]. -Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] for function, method, and class definitions. -[The Zen of Python (PEP 20)][the zen of python] and [_What is Pythonic?_][what is pythonic] lay out additional philosophies. +This exercise introduces 4 major Python language features: Names (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. -Objects are [assigned][assignment statements] to [names][naming and binding] via the _assignment operator_, `=`. -[Variables][variables] are written in [`snake_case`][snake case], and _constants_ usually in `SCREAMING_SNAKE_CASE`. -A `name` (_variable or constant_) is not itself typed, and can be attached or re-attached to different objects over its lifetime. -For extended naming conventions and advice, see [PEP 8][pep8]. +~~~~exercism/note + +In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names. + +~~~~ + + +## Name Assignment and Re-assignment + + +There are no keywords in Python to define variables or constants and there is no difference in the way Python treats them. +Both are considered [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. +On Exercism, [variables][variables] are always written in [`snake_case`][snake case], and _constants_ in `SCREAMING_SNAKE_CASE`. + +Names are assigned to values via `=`, or the [_assignment operator_][assignment statements]: ` = `. +A name (_variable or constant_) can be assigned or re-assigned over its lifetime to different values/different object types. +For example, `my_first_variable` can be assigned and re-assigned many times using `=`, and can refer to different object types with each re-assignment: ```python +# Assigning my_first_variable to a numeric value. >>> my_first_variable = 1 ->>> my_first_variable = "Last one, I promise" +>>> print(type(my_first_variable)) +... + + >>> print(my_first_variable) ... -"Last one, I promise" +1 + +# Reassigning my_first_variable to a new string value. +>>> my_first_variable = "Now, I'm a string." +>>> print(type(my_first_variable)) +... + + +>>> print(my_first_variable) +... +"Now, I'm a string." ``` -Constants are typically defined on a [module][module] or _global_ level, and although they _can_ be changed, they are _intended_ to be named only once. -Their `SCREAMING_SNAKE_CASE` is a message to other developers that the assignment should not be altered: +### Constants + +Constants are typically defined at a [module][module] level, being values that are accessible outside function or class scope. +Constant names **_can be reassigned to new values_**, but they are _intended_ to be named only once. +Using `SCREAMING_SNAKE_CASE` warns other programmers that these names should not be mutated or reassigned. + ```python # All caps signal that this is intended as a constant. @@ -31,23 +61,43 @@ MY_FIRST_CONSTANT = 16 # Re-assignment will be allowed by the compiler & interpreter, # but this is VERY strongly discouraged. -# Please don't do: MY_FIRST_CONSTANT = "Some other value" +# Please don't: MY_FIRST_CONSTANT = "Some other value" ``` + +## Functions + The keyword `def` begins a [function definition][function definition]. It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. Parameters can be of several different varieties, and can even [vary][more on functions] in length. The `def` line is terminated with a colon. +```python +# function definition +def my_function_name(parameter, second_parameter): + + +``` + Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent for all indented statements_. + +```python +# Function definition on first line. +def add_two_numbers(number_one, number_two): + print(number_one + number_two) # Prints the sum of the numbers, and is indented by 2 spaces. + +>>> add_two_numbers(3, 4) +7 +``` + Functions explicitly return a value or object via the [`return`][return] keyword. ```python # Function definition on first line. def add_two_numbers(number_one, number_two): - return number_one + number_two # Returns the sum of the numbers, and is indented by 2 spaces. + return number_one + number_two # Returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 @@ -64,33 +114,28 @@ def add_two_numbers(number_one, number_two): None ``` -Inconsistent indentation will raise an error: +While you may choose any indentation depth, _inconsistent_ indentation in your code blocks will raise an error: ```python # The return statement line does not match the first line indent. >>> def add_three_numbers_misformatted(number_one, number_two, number_three): ... result = number_one + number_two + number_three # Indented by 4 spaces. ... return result #this was only indented by 3 spaces +... +... File "", line 3 return result ^ IndentationError: unindent does not match any outer indentation level ``` -Functions are [_called_][calls] using their name followed by `()`. -The number of arguments passed in the parentheses must match the number of parameters in the original function definition unless [default arguments][default arguments] have been used: +### Calling Functions + +Functions are [_called_][calls] or invoked using their name followed by `()`. +The number of arguments passed in the parentheses must match the number of parameters in the original function definition.. ```python ->>> def number_to_the_power_of(number_one, number_two): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. - """ - +>>> def number_to_the_power_of(number_one, number_two): return number_one ** number_two ... @@ -98,7 +143,7 @@ The number of arguments passed in the parentheses must match the number of param 27 ``` -A mis-match between parameters and arguments will raise an error: +A mis-match between the number of parameters and the number of arguments will raise an error: ```python >>> number_to_the_power_of(4,) @@ -109,48 +154,34 @@ TypeError: number_to_the_power_of() missing 1 required positional argument: 'num ``` -Adding a [default value][default arguments] for a parameter can defend against such errors: +Calling functions defined inside a class (_class methods_) use `.()`, otherwise known as dot (.) notation: ```python ->>> def number_to_the_power_of_default(number_one, number_two=2): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. - """ - - return number_one ** number_two +# This is an example of a method call of the built in str class. +# Define a variable and assign it to a string. +>>> start_text = "my silly sentence for examples." -... ->>> number_to_the_power_of_default(4) -16 -``` +# Uppercase the string by calling the upper method from the str class. +>>> str.upper(start_text) +"MY SILLY SENTENCE FOR EXAMPLES." -Methods bound to class names are invoked via dot notation (.), as are functions, constants, or global names imported as part of a module.: -```python +# Below is an example of a method call of the built in list class. +# Define an empty list +>>> my_list = [] -import string +# Add an element to the list by calling the append method from the list class. +>>> my_list.append(start_text) +>>> print(my_list) +["my silly sentence for examples."] +``` -# This is a constant provided by the *string* module. ->>> print(string.ascii_lowercase) -"abcdefghijklmnopqrstuvwxyz" -# This is a method call of the str *class*. ->>> start_text = "my silly sentence for examples." ->>> str.upper(start_text) -"MY SILLY SENTENCE FOR EXAMPLES." +## Comments -# This is a method call of an *instance* of the str *class*. ->>> start_text.upper() -"MY SILLY SENTENCE FOR EXAMPLES." -``` [Comments][comments] in Python start with a `#` that is not part of a string, and end at line termination. -Unlike many other programming languages, Python does not support multi-line comment marks. +Unlike many other programming languages, Python **does not support** multi-line comment marks. Each line of a comment block must start with the `#` character. Comments are ignored by the interpreter: @@ -165,10 +196,33 @@ x = "foo" # This is an in-line comment. # these should be used sparingly. ``` +## Docstrings + The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose. -Docstrings are read by automated documentation tools and are returned by calling `.__doc__` on the function, method, or class name. -They can also function as [lightweight unit tests][doctests], which will be covered in a later exercise. -They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][PEP257]: +Docstrings are declared using triple double quotes (""") indented at the same level as the code block: + + +```python + +# An example from PEP257 of a multi-line docstring. +def complex(real=0.0, imag=0.0): + """Form a complex number. + + Keyword arguments: + real -- the real part (default 0.0) + imag -- the imaginary part (default 0.0) + """ + + if imag == 0.0 and real == 0.0: + return complex_zero + ... + +``` + +Docstrings are read by automated documentation tools and are returned by calling the special attribute `.__doc__` on the function, method, or class name. +Docstrings can also function as [lightweight unit tests][doctests], which will be covered in a later exercise. +They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][PEP257]. + ```python # An example on a user-defined function. @@ -185,6 +239,7 @@ They are recommended for programs of any size where documentation is needed, and return number_one ** number_two ... +# Calling the .__doc__ attribute of the function and printing the result. >>> print(number_to_the_power_of.__doc__) Raise a number to an arbitrary power. @@ -194,7 +249,7 @@ Raise a number to an arbitrary power. Takes number_one and raises it to the power of number_two, returning the result. -# __doc__() for the built-in type: str. +# Printing the __doc__ attribute for the built-in type: str. >>> print(str.__doc__) str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str @@ -212,27 +267,21 @@ errors defaults to 'strict'. [assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements [calls]: https://docs.python.org/3/reference/expressions.html#calls [comments]: https://realpython.com/python-comments-guide/#python-commenting-basics -[default arguments]: https://docs.python.org/3/tutorial/controlflow.html#default-argument-values [docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings [doctests]: https://docs.python.org/3/library/doctest.html [duck typing]: https://en.wikipedia.org/wiki/Duck_typing [dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed [everythings an object]: https://docs.python.org/3/reference/datamodel.html +[facts-and-myths-about-python-names]: https://nedbatchelder.com/text/names.html [function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing [indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation [module]: https://docs.python.org/3/tutorial/modules.html [more on functions]: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions -[naming and binding]: https://docs.python.org/3/reference/executionmodel.html#naming-and-binding [none]: https://docs.python.org/3/library/constants.html [object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming [parameters]: https://docs.python.org/3/glossary.html#term-parameter -[pep8]: https://www.python.org/dev/peps/pep-0008/ -[python docs]: https://docs.python.org/3/ [return]: https://docs.python.org/3/reference/simple_stmts.html#return -[significant indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation [snake case]: https://en.wikipedia.org/wiki/Snake_case -[the zen of python]: https://www.python.org/dev/peps/pep-0020/ [type hints]: https://docs.python.org/3/library/typing.html [variables]: https://realpython.com/python-variables/ -[what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.html diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py index b08e1a7149..823529cbf5 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py @@ -3,12 +3,37 @@ Learn about Guido, the creator of the Python language: https://en.wikipedia.org/wiki/Guido_van_Rossum """ -# TODO: define the 'EXPECTED_BAKE_TIME' constant -# TODO: consider defining the 'PREPARATION_TIME' constant -# equal to the time it takes to prepare a single layer - - -# TODO: define the 'bake_time_remaining()' function +############################################################################################################## +# Hi There 👋🏽 +# For much of the Python syllabus, we will provide function stubs + docstrings. +# These are the expected function names with details to help you get started solving the exercise. +# In general, they hint at expected functionality, along with expected input and return types. +# +# However, you can completely clear out the stubs before you start coding, although we recommend that you keep +# them, because they are already set up to work with the tests, which you can find in ./lasagna_test.py. +# If the tests don't find the expected function names, they will throw import errors. +# +# ❗PLEASE NOTE❗ We are deviating a bit in this first exercise by asking you to practice defining +# functions & docstrings. We give you one completed stub below (bake_time_remaining), but have omitted the stubs and +# docstrings for the remaining two functions. +# +# We recommend copying the first stub + docstring, and then changing details like the function name +# and docstring text to match what is asked for in the #TODO comments and instructions before getting started +# with writing the code for each function body. +# +# ⭐ PS: You should remove explanation comment blocks like this one, and should also +# remove any comment blocks that start with #TODO (or our analyzer will nag you about them) +################################################################################################################ + + +# TODO: define the 'EXPECTED_BAKE_TIME' constant below. + + +# TODO: consider defining a 'PREPARATION_TIME' constant +# equal to the time it takes to prepare a single layer. + + +# TODO: Remove 'pass' and complete the 'bake_time_remaining()' function below. def bake_time_remaining(): """Calculate the bake time remaining. @@ -23,8 +48,11 @@ def bake_time_remaining(): pass -# TODO: define the 'preparation_time_in_minutes()' function -# and consider using 'PREPARATION_TIME' here +# TODO: Define the 'preparation_time_in_minutes()' function below. +# Remember to add a docstring (you can copy and then alter the one from bake_time_remaining.) +# You might also consider using 'PREPARATION_TIME' here, if you have it defined. + -# TODO: define the 'elapsed_time_in_minutes()' function +# TODO: define the 'elapsed_time_in_minutes()' function below. +# Remember to add a docstring (you can copy and then alter the one from bake_time_remaining.) From 40894b621f846f72068637e158e5db74c044bb83 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 18 Feb 2023 01:12:31 -0800 Subject: [PATCH 349/826] More format fiddling. --- .../guidos-gorgeous-lasagna/lasagna.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py index 823529cbf5..6b73485127 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py @@ -9,31 +9,31 @@ # These are the expected function names with details to help you get started solving the exercise. # In general, they hint at expected functionality, along with expected input and return types. # -# However, you can completely clear out the stubs before you start coding, although we recommend that you keep -# them, because they are already set up to work with the tests, which you can find in ./lasagna_test.py. -# If the tests don't find the expected function names, they will throw import errors. +# However, you can completely clear out the stubs before you start coding. We recommend that +# you keep them, because they are already set up to work with the tests, which you can find in +# ./lasagna_test.py. If the tests don't find the expected names, they will throw import errors. # # ❗PLEASE NOTE❗ We are deviating a bit in this first exercise by asking you to practice defining -# functions & docstrings. We give you one completed stub below (bake_time_remaining), but have omitted the stubs and -# docstrings for the remaining two functions. +# functions & docstrings. We give you one completed stub below (bake_time_remaining), but have +# omitted the stubs and docstrings for the remaining two functions. # # We recommend copying the first stub + docstring, and then changing details like the function name -# and docstring text to match what is asked for in the #TODO comments and instructions before getting started -# with writing the code for each function body. +# and docstring text to match what is asked for in the #TODO comments and instructions. +# Once you have the correct stubs, you can get started with writing the code for each function body. # # ⭐ PS: You should remove explanation comment blocks like this one, and should also # remove any comment blocks that start with #TODO (or our analyzer will nag you about them) ################################################################################################################ -# TODO: define the 'EXPECTED_BAKE_TIME' constant below. +#TODO: define the 'EXPECTED_BAKE_TIME' constant below. -# TODO: consider defining a 'PREPARATION_TIME' constant +#TODO: consider defining a 'PREPARATION_TIME' constant # equal to the time it takes to prepare a single layer. -# TODO: Remove 'pass' and complete the 'bake_time_remaining()' function below. +#TODO: Remove 'pass' and complete the 'bake_time_remaining()' function below. def bake_time_remaining(): """Calculate the bake time remaining. @@ -48,11 +48,11 @@ def bake_time_remaining(): pass -# TODO: Define the 'preparation_time_in_minutes()' function below. +#TODO: Define the 'preparation_time_in_minutes()' function below. # Remember to add a docstring (you can copy and then alter the one from bake_time_remaining.) # You might also consider using 'PREPARATION_TIME' here, if you have it defined. -# TODO: define the 'elapsed_time_in_minutes()' function below. +#TODO: define the 'elapsed_time_in_minutes()' function below. # Remember to add a docstring (you can copy and then alter the one from bake_time_remaining.) From b8325af39eafb88f9330c0d53d18114cf6215336 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 18 Feb 2023 01:17:22 -0800 Subject: [PATCH 350/826] Ran prittier and corrected some spelling errors. --- .../guidos-gorgeous-lasagna/.docs/hints.md | 6 +-- .../.docs/instructions.md | 2 +- .../.docs/introduction.md | 41 +++++++++---------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md index 351daef986..bdab2e7eb3 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md @@ -4,7 +4,7 @@ - [The Python Tutorial][the python tutorial] can be a great introduction. - [Numbers][numbers] in Python can be integers, floats, or complex. -- [PEP 8][PEP8] is the Python code style guide. +- [PEP 8][pep8] is the Python code style guide. ## 1. Define expected bake time in minutes @@ -32,15 +32,15 @@ ## 5. Update the recipe with notes -- Clearly [commenting][comments] and [documenting][docstrings] your code according to [PEP257][PEP257] is always recommended. +- Clearly [commenting][comments] and [documenting][docstrings] your code according to [PEP257][pep257] is always recommended. -[PEP257]: https://www.python.org/dev/peps/pep-0257/ [assignment]: https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment-stmt [comments]: https://realpython.com/python-comments-guide/ [defining functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [docstrings]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings [naming]: https://realpython.com/python-variables/ [numbers]: https://docs.python.org/3/tutorial/introduction.html#numbers +[pep257]: https://www.python.org/dev/peps/pep-0257/ [python as a calculator]: https://docs.python.org/3/tutorial/introduction.html#using-python-as-a-calculator [return]: https://docs.python.org/3/reference/simple_stmts.html#return [the python tutorial]: https://docs.python.org/3/tutorial/introduction.html diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md index f4b9480e50..c3a0982e16 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md @@ -56,7 +56,7 @@ def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): """ Return elapsed cooking time. - This function takes two numbers representing the number of layers & the time already spent + This function takes two numbers representing the number of layers & the time already spent baking and calculates the total elapsed minutes spent cooking the lasagna. """ ``` diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index e9d5b96db6..377f77d197 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -2,10 +2,9 @@ Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. -Python supports Imperative, declarative (e.g. functional), and object oriented programming _styles_, but internally [everything in Python is an object][everythings an object]. - -This exercise introduces 4 major Python language features: Names (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. +Python supports Imperative, declarative (e.g., functional), and object-oriented programming _styles_, but internally [everything in Python is an object][everythings an object]. +This exercise introduces 4 major Python language features: Names (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. ~~~~exercism/note @@ -13,10 +12,8 @@ In general, content, tests, and analyzer tooling for the Python track follow the ~~~~ - ## Name Assignment and Re-assignment - There are no keywords in Python to define variables or constants and there is no difference in the way Python treats them. Both are considered [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. On Exercism, [variables][variables] are always written in [`snake_case`][snake case], and _constants_ in `SCREAMING_SNAKE_CASE`. @@ -47,14 +44,12 @@ For example, `my_first_variable` can be assigned and re-assigned many times usin "Now, I'm a string." ``` - ### Constants Constants are typically defined at a [module][module] level, being values that are accessible outside function or class scope. Constant names **_can be reassigned to new values_**, but they are _intended_ to be named only once. Using `SCREAMING_SNAKE_CASE` warns other programmers that these names should not be mutated or reassigned. - ```python # All caps signal that this is intended as a constant. MY_FIRST_CONSTANT = 16 @@ -64,25 +59,24 @@ MY_FIRST_CONSTANT = 16 # Please don't: MY_FIRST_CONSTANT = "Some other value" ``` - ## Functions The keyword `def` begins a [function definition][function definition]. It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. - Parameters can be of several different varieties, and can even [vary][more on functions] in length. +Parameters can be of several different varieties, and can even [vary][more on functions] in length. The `def` line is terminated with a colon. ```python # function definition def my_function_name(parameter, second_parameter): - + ``` + Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent for all indented statements_. - ```python # Function definition on first line. def add_two_numbers(number_one, number_two): @@ -92,6 +86,7 @@ def add_two_numbers(number_one, number_two): 7 ``` + Functions explicitly return a value or object via the [`return`][return] keyword. ```python @@ -103,6 +98,7 @@ def add_two_numbers(number_one, number_two): 7 ``` + Functions that do not have an explicit `return` expression will return [`None`][none]. ```python @@ -114,6 +110,7 @@ def add_two_numbers(number_one, number_two): None ``` + While you may choose any indentation depth, _inconsistent_ indentation in your code blocks will raise an error: ```python @@ -122,20 +119,21 @@ While you may choose any indentation depth, _inconsistent_ indentation in your c ... result = number_one + number_two + number_three # Indented by 4 spaces. ... return result #this was only indented by 3 spaces ... -... +... File "", line 3 return result ^ IndentationError: unindent does not match any outer indentation level ``` + ### Calling Functions Functions are [_called_][calls] or invoked using their name followed by `()`. The number of arguments passed in the parentheses must match the number of parameters in the original function definition.. ```python ->>> def number_to_the_power_of(number_one, number_two): +>>> def number_to_the_power_of(number_one, number_two): return number_one ** number_two ... @@ -143,6 +141,7 @@ The number of arguments passed in the parentheses must match the number of param 27 ``` + A mis-match between the number of parameters and the number of arguments will raise an error: ```python @@ -154,6 +153,7 @@ TypeError: number_to_the_power_of() missing 1 required positional argument: 'num ``` + Calling functions defined inside a class (_class methods_) use `.()`, otherwise known as dot (.) notation: ```python @@ -179,7 +179,6 @@ Calling functions defined inside a class (_class methods_) use `.>> def number_to_the_power_of(number_one, number_two): """Raise a number to an arbitrary power. - + :param number_one: int the base number. :param number_two: int the power to raise the base number to. :return: int - number raised to power of second number - + Takes number_one and raises it to the power of number_two, returning the result. """ @@ -263,7 +262,7 @@ encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'. ``` -[PEP257]: https://www.python.org/dev/peps/pep-0257/ +[pep257]: https://www.python.org/dev/peps/pep-0257/ [assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements [calls]: https://docs.python.org/3/reference/expressions.html#calls [comments]: https://realpython.com/python-comments-guide/#python-commenting-basics From 2edd099307faeb1a89880cc5ff56537079ffcb12 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 18 Feb 2023 01:51:01 -0800 Subject: [PATCH 351/826] And more formatting. --- .../.docs/introduction.md | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index 377f77d197..ce0ef3abab 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -2,9 +2,10 @@ Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. -Python supports Imperative, declarative (e.g., functional), and object-oriented programming _styles_, but internally [everything in Python is an object][everythings an object]. +Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally [everything in Python is an object][everythings an object]. + +This exercise introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. -This exercise introduces 4 major Python language features: Names (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. ~~~~exercism/note @@ -12,38 +13,37 @@ In general, content, tests, and analyzer tooling for the Python track follow the ~~~~ + ## Name Assignment and Re-assignment -There are no keywords in Python to define variables or constants and there is no difference in the way Python treats them. -Both are considered [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. +In Python, there are no keywords to define variables or constants. +Both are [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. On Exercism, [variables][variables] are always written in [`snake_case`][snake case], and _constants_ in `SCREAMING_SNAKE_CASE`. -Names are assigned to values via `=`, or the [_assignment operator_][assignment statements]: ` = `. -A name (_variable or constant_) can be assigned or re-assigned over its lifetime to different values/different object types. -For example, `my_first_variable` can be assigned and re-assigned many times using `=`, and can refer to different object types with each re-assignment: +Names are assigned to values using `=`, or the [_assignment operator_][assignment statements] (` = `). +A name (_variable or constant_) can be re-assigned over its lifetime to different values/object types. + +For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment: ```python # Assigning my_first_variable to a numeric value. >>> my_first_variable = 1 >>> print(type(my_first_variable)) -... >>> print(my_first_variable) -... 1 # Reassigning my_first_variable to a new string value. >>> my_first_variable = "Now, I'm a string." >>> print(type(my_first_variable)) -... >>> print(my_first_variable) -... "Now, I'm a string." ``` + ### Constants Constants are typically defined at a [module][module] level, being values that are accessible outside function or class scope. @@ -59,15 +59,17 @@ MY_FIRST_CONSTANT = 16 # Please don't: MY_FIRST_CONSTANT = "Some other value" ``` + ## Functions The keyword `def` begins a [function definition][function definition]. It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. Parameters can be of several different varieties, and can even [vary][more on functions] in length. -The `def` line is terminated with a colon. + +The `def` line is terminated with a colon: ```python -# function definition +# Function definition. def my_function_name(parameter, second_parameter): @@ -75,7 +77,8 @@ def my_function_name(parameter, second_parameter): Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. -There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent for all indented statements_. +There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent_ for all indented statements. + ```python # Function definition on first line. @@ -92,7 +95,7 @@ Functions explicitly return a value or object via the [`return`][return] keyword ```python # Function definition on first line. def add_two_numbers(number_one, number_two): - return number_one + number_two # Returns the sum of the numbers. + return number_one + number_two # Returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 @@ -185,6 +188,7 @@ Each line of a comment block must start with the `#` character. Comments are ignored by the interpreter: + ```python # This is a single line comment. @@ -201,6 +205,7 @@ x = "foo" # This is an in-line comment. The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose. Docstrings are declared using triple double quotes (""") indented at the same level as the code block: + ```python # An example from PEP257 of a multi-line docstring. From a552877a219059f95b3744cb31c6f6d507070ccd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 18 Feb 2023 02:00:26 -0800 Subject: [PATCH 352/826] Last formatting niggles. --- .../guidos-gorgeous-lasagna/.docs/introduction.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index ce0ef3abab..5f4ab830bc 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -133,7 +133,7 @@ IndentationError: unindent does not match any outer indentation level ### Calling Functions Functions are [_called_][calls] or invoked using their name followed by `()`. -The number of arguments passed in the parentheses must match the number of parameters in the original function definition.. +The number of arguments passed in the parentheses must match the number of parameters in the original function definition. ```python >>> def number_to_the_power_of(number_one, number_two): @@ -157,7 +157,7 @@ TypeError: number_to_the_power_of() missing 1 required positional argument: 'num ``` -Calling functions defined inside a class (_class methods_) use `.()`, otherwise known as dot (.) notation: +Calling functions defined inside a class (_methods_) use `.()`, otherwise known as dot (.) notation: ```python # This is an example of a method call of the built in str class. @@ -219,15 +219,16 @@ def complex(real=0.0, imag=0.0): if imag == 0.0 and real == 0.0: return complex_zero - ... ``` Docstrings are read by automated documentation tools and are returned by calling the special attribute `.__doc__` on the function, method, or class name. -Docstrings can also function as [lightweight unit tests][doctests], which will be covered in a later exercise. They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][pep257]. +Docstrings can also function as [lightweight unit tests][doctests], which will be covered in a later exercise. + + ```python # An example on a user-defined function. >>> def number_to_the_power_of(number_one, number_two): @@ -253,6 +254,8 @@ Raise a number to an arbitrary power. Takes number_one and raises it to the power of number_two, returning the result. + + # Printing the __doc__ attribute for the built-in type: str. >>> print(str.__doc__) str(object='') -> str From c9d96f4ec02a173448573ce8eb964dea47aa992f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 20 Feb 2023 13:40:59 -0800 Subject: [PATCH 353/826] Rewrites of about, introduction, and links. --- concepts/basics/about.md | 223 +++++++++++++++++++++--------- concepts/basics/introduction.md | 238 ++++++++++++++++++++------------ concepts/basics/links.json | 12 -- 3 files changed, 311 insertions(+), 162 deletions(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index ddcc57790a..70db44451f 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -1,14 +1,16 @@ # basics -[Python][python docs] is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. +Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. -It supports multiple programming paradigms including both imperative (_object-oriented, procedural_) and declarative (_functional, concurrent_) flavors. -But do not be fooled: while programming across paradigms is fully _supported_, [everything in Python is an object][everythings an object]. +Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally [everything in Python is an object][everythings an object]. + +Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. + + +The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies Python was created by Guido van Rossum and first released in 1991. The [Python Software Foundation][psf] manages and directs resources for Python and CPython development and receives proposals for changes to the language from [members][psf membership] of the community via [Python Enhancement Proposals or PEPs][peps]. -Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. -The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies. Complete documentation for the current release can be found at [docs.python.org][python docs]. @@ -19,35 +21,64 @@ Complete documentation for the current release can be found at [docs.python.org] - [Python FAQs][python faqs] - [Python Glossary of Terms][python glossary of terms] +This concept introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. + + +~~~~exercism/note -## Getting Started +In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names. -Objects are [assigned][assignment statements] to [names][naming and binding] in Python via the `=` or _assignment operator_. [Variables][variables] are written in [`snake_case`][snake case], and constants usually in `SCREAMING_SNAKE_CASE`. +~~~~ -A `name` (_variable or constant_) is not itself typed, and can be attached or re-attached to different objects or values over its lifetime. -For extended naming conventions and formatting advice, see [PEP 8][pep8]. + +## Name Assignment and Re-assignment + +In Python, there are no keywords to define variables or constants. +Both are [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. +On Exercism, [variables][variables] are always written in [`snake_case`][snake case], and _constants_ in `SCREAMING_SNAKE_CASE`. + +Names are assigned to values using `=`, or the [_assignment operator_][assignment statements] (` = `). +A name (_variable or constant_) can be re-assigned over its lifetime to different values/object types. + +For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment: ```python +# Assigning my_first_variable to a numeric value. >>> my_first_variable = 1 ->>> my_first_variable = "Last one, I promise" +>>> print(type(my_first_variable)) + + >>> print(my_first_variable) +1 + +# Reassigning my_first_variable to a new string value. +>>> my_first_variable = "Now, I'm a string." +>>> print(type(my_first_variable)) + -"Last one, I promise" +>>> print(my_first_variable) +"Now, I'm a string." ``` -Constants are usually defined on a [module][module] or `global` level, and although they _can_ be changed, they are _intended_ to be assigned only once. -Their `SCREAMING_SNAKE_CASE` is a message to other developers that the assignment should not be altered. +### Constants + +Constants are typically defined at a [module][module] level, being values that are accessible outside function or class scope. +Constant names **_can be reassigned to new values_**, but they are _intended_ to be named only once. +Using `SCREAMING_SNAKE_CASE` warns other programmers that these names should not be mutated or reassigned. ```python -# All caps signal that this is intended as a constant +# All caps signal that this is intended as a constant. MY_FIRST_CONSTANT = 16 # Re-assignment will be allowed by the compiler & interpreter, -# but is VERY strongly discouraged. -# Please don't do: MY_FIRST_CONSTANT = "Some other value" +# but this is VERY strongly discouraged. +# Please don't: MY_FIRST_CONSTANT = "Some other value" ``` + +## Functions + In Python, units of functionality are encapsulated in [_functions._][functions], which are themselves [objects][objects] (_it's [turtles all the way down][turtles all the way down]_). Functions can be executed by themselves, passed as arguments to other functions, nested, or bound to a class. @@ -55,23 +86,45 @@ When functions are bound to a [class][classes] name, they're referred to as [met Related functions and classes (_with their methods_) can be grouped together in the same file or module, and imported in part or in whole for use in other programs. The keyword `def` begins a [function definition][function definition]. -`def` must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. - Parameters can be of several different varieties, and can even [vary][more on functions] in length. -The `def` line is terminated with a colon (`:`). +It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. +Parameters can be of several different varieties, and can even [vary][more on functions] in length. + +The `def` line is terminated with a colon (`:`): + +```python +# Function definition. +def my_function_name(parameter, second_parameter): + + +``` + +Statements for the `function body` begin on the line following `def` and must be _indented in a block_. +There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent_ for all indented statements. + + +```python +# Function definition on first line. +def add_two_numbers(number_one, number_two): + print(number_one + number_two) # Prints the sum of the numbers, and is indented by 2 spaces. + +>>> add_two_numbers(3, 4) +7 +``` + -Statements for the `function body` begin on the line following `def`, and must be _indented in a block_. -There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent for all indented statements_. Functions explicitly return a value or object via the [`return`][return] keyword. ```python # Function definition on first line. ->>> def add_two_numbers(number_one, number_two): -... return number_one + number_two # Returns the sum of the numbers, and is indented by 2 spaces. +def add_two_numbers(number_one, number_two): + return number_one + number_two # Returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 ``` + + Functions that do not have an explicit `return` expression will return [`None`][none]. ```python @@ -83,53 +136,53 @@ def add_two_numbers(number_one, number_two): None ``` -Inconsistent indentation will raise an error: +While you may choose any indentation depth, _inconsistent_ indentation in your code blocks will raise an error: ```python # The return statement line does not match the first line indent. >>> def add_three_numbers_misformatted(number_one, number_two, number_three): ... result = number_one + number_two + number_three # Indented by 4 spaces. ... return result #this was only indented by 3 spaces +... +... File "", line 3 return result ^ IndentationError: unindent does not match any outer indentation level ``` + +### Calling Functions + Functions are [_called_][calls] using their name followed by `()`. The number of arguments passed in the parentheses must match the number of parameters in the original function definition unless [default arguments][default arguments] have been used. ```python >>> def number_to_the_power_of(number_one, number_two): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. - """ - -... return number_one ** number_two - + return number_one ** number_two +... >>> number_to_the_power_of(3,3) 27 ``` -A mis-match between parameters and arguments will raise an error: + +A mis-match between the number of parameters and the number of arguments will raise an error: ```python >>> number_to_the_power_of(4,) +... Traceback (most recent call last): File "", line 1, in TypeError: number_to_the_power_of() missing 1 required positional argument: 'number_two' ``` + Adding a [default value][default arguments] for a parameter can defend against such errors: ```python +# Note the default value of 2 assigned below. def number_to_the_power_of_default(number_one, number_two=2): """Raise a number to an arbitrary power. @@ -142,35 +195,51 @@ def number_to_the_power_of_default(number_one, number_two=2): return number_one ** number_two +# Because there was a default value, this call with only one argument does not throw an error. >>> number_to_the_power_of_default(4) 16 ``` -Methods bound to class names are invoked via dot notation (`.()`), as are functions, constants, or global names imported as part of a module.: + +Methods bound to class names are invoked via dot notation (`.())`, as are functions (`.()`), constants (`.`), or any other global names imported as part of a module.: ```python +# This is an example of a method call of the built in str class. +# Define a variable and assign it to a string. +>>> start_text = "my silly sentence for examples." + +# Uppercase the string by calling the upper method from the str class. +>>> str.upper(start_text) +"MY SILLY SENTENCE FOR EXAMPLES." + + +# Below is an example of a method call of the built in list class. +# Define an empty list +>>> my_list = [] + +# Add an element to the list by calling the append method from the list class. +>>> my_list.append(start_text) +>>> print(my_list) +["my silly sentence for examples."] import string # This is a constant provided by the *string* module. ->>> print(string.ascii_lowercase) +>>> alphabet = string.ascii_lowercase +>>> print(alphabet) "abcdefghijklmnopqrstuvwxyz" +``` -# This is a method call of the str *class*. ->>> start_text = "my silly sentence for examples." ->>> str.upper(start_text) -"MY SILLY SENTENCE FOR EXAMPLES." -# This is a method call of an *instance* of the str *class*. ->>> start_text.upper() -"MY SILLY SENTENCE FOR EXAMPLES." -``` +## Comments [Comments][comments] in Python start with a `#` that is not part of a string, and end at line termination. -Unlike many other programming languages, Python does not support multi-line comment marks. +Unlike many other programming languages, Python **does not support** multi-line comment marks. Each line of a comment block must start with the `#` character. + Comments are ignored by the interpreter: + ```python # This is a single line comment. @@ -181,25 +250,53 @@ x = "foo" # This is an in-line comment. # these should be used sparingly. ``` + +## Docstrings + The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose. -Docstrings are read by automated documentation tools and are returned by calling `.__doc__` on the function, method, or class name. -They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][PEP257]: +Docstrings are declared using triple double quotes (""") indented at the same level as the code block: ```python -# An example on a user-defined function. -def number_to_the_power_of(number_one, number_two): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. + +# An example from PEP257 of a multi-line docstring. +def complex(real=0.0, imag=0.0): + """Form a complex number. + + Keyword arguments: + real -- the real part (default 0.0) + imag -- the imaginary part (default 0.0) """ - return number_one ** number_two + if imag == 0.0 and real == 0.0: + return complex_zero + +``` + + +Docstrings are read by automated documentation tools and are returned by calling the special attribute `.__doc__` on the function, method, or class name. +They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][pep257]. + +Docstrings can also function as [lightweight unit tests][doctests], which can be read and run by PyTest, or by importing the `doctest` module. +Testing and `doctest` will be covered in a later concept. + + +```python +# An example on a user-defined function. +>>> def number_to_the_power_of(number_one, number_two): + """Raise a number to an arbitrary power. + + :param number_one: int the base number. + :param number_two: int the power to raise the base number to. + :return: int - number raised to power of second number + + Takes number_one and raises it to the power of number_two, returning the result. + """ + + return number_one ** number_two +... +# Calling the .__doc__ attribute of the function and printing the result. >>> print(number_to_the_power_of.__doc__) Raise a number to an arbitrary power. @@ -209,7 +306,9 @@ Raise a number to an arbitrary power. Takes number_one and raises it to the power of number_two, returning the result. -# __doc__() for the built-in type: str. + + +# Printing the __doc__ attribute for the built-in type: str. >>> print(str.__doc__) str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str @@ -223,9 +322,6 @@ encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'. ``` -Docstrings can also include [doctests][doctests], which are interactive examples of how a method or function should work. -Doctests can be read and run by PyTest, or by importing the `doctest` module. - [PEP257]: https://www.python.org/dev/peps/pep-0257/ [assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements [calls]: https://docs.python.org/3/reference/expressions.html#calls @@ -237,6 +333,7 @@ Doctests can be read and run by PyTest, or by importing the `doctest` module. [duck typing]: https://en.wikipedia.org/wiki/Duck_typing [dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed [everythings an object]: https://docs.python.org/3/reference/datamodel.html +[facts-and-myths-about-python-names]: https://nedbatchelder.com/text/names.html [function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [functions]: https://docs.python.org/3/reference/compound_stmts.html#function [gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index ca69d5af68..ef002e7cd3 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -1,27 +1,54 @@ # Introduction -[Python][python docs] is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. -It employs both [duck typing][duck typing] and [gradual typing][gradual typing] via [type hints][type hints]. -It supports multiple programming paradigms including both imperative (_object-oriented, procedural_) and declarative (_functional, concurrent_) flavors. +Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. +It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. +Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally [everything in Python is an object][everythings an object]. -Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] for function, method, and class definitions. -The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies. +This concept introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. -Objects are [assigned][assignment statements] to [names][naming and binding] via the _assignment operator_, `=`. -[Variables][variables] are written in [`snake_case`][snake case], and _constants_ usually in `SCREAMING_SNAKE_CASE`. -A `name` (_variable or constant_) is not itself _typed_, and can be attached or re-attached to different objects over its lifetime. -For extended naming conventions and advice, see [PEP 8][pep8]. + +~~~~exercism/note + +In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names. + +~~~~ + + +## Name Assignment and Re-assignment + +In Python, there are no keywords to define variables or constants. +Both are [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. +On Exercism, [variables][variables] are always written in [`snake_case`][snake case], and _constants_ in `SCREAMING_SNAKE_CASE`. + +Names are assigned to values using `=`, or the [_assignment operator_][assignment statements] (` = `). +A name (_variable or constant_) can be re-assigned over its lifetime to different values/object types. + +For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment: ```python +# Assigning my_first_variable to a numeric value. >>> my_first_variable = 1 ->>> my_first_variable = "Last one, I promise" +>>> print(type(my_first_variable)) + + >>> print(my_first_variable) +1 -"Last one, I promise" +# Reassigning my_first_variable to a new string value. +>>> my_first_variable = "Now, I'm a string." +>>> print(type(my_first_variable)) + + +>>> print(my_first_variable) +"Now, I'm a string." ``` -Constants are typically defined on a [module][module] or _global_ level, and although they _can_ be changed, they are _intended_ to be named only once. -Their `SCREAMING_SNAKE_CASE` is a message to other developers that the assignment should not be altered: + +### Constants + +Constants are typically defined at a [module][module] level, being values that are accessible outside function or class scope. +Constant names **_can be reassigned to new values_**, but they are _intended_ to be named only once. +Using `SCREAMING_SNAKE_CASE` warns other programmers that these names should not be mutated or reassigned. ```python # All caps signal that this is intended as a constant. @@ -29,27 +56,52 @@ MY_FIRST_CONSTANT = 16 # Re-assignment will be allowed by the compiler & interpreter, # but this is VERY strongly discouraged. -# Please don't do: MY_FIRST_CONSTANT = "Some other value" +# Please don't: MY_FIRST_CONSTANT = "Some other value" ``` + +## Functions + The keyword `def` begins a [function definition][function definition]. It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. - Parameters can be of several different varieties, and can even [vary][more on functions] in length. -The `def` line is terminated with a colon. +Parameters can be of several different varieties, and can even [vary][more on functions] in length. + +The `def` line is terminated with a colon: + +```python +# Function definition. +def my_function_name(parameter, second_parameter): + + +``` + + +Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. +There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent_ for all indented statements. + + +```python +# Function definition on first line. +def add_two_numbers(number_one, number_two): + print(number_one + number_two) # Prints the sum of the numbers, and is indented by 2 spaces. + +>>> add_two_numbers(3, 4) +7 +``` + -Statements for the _body_ of the function begin on the line following `def`, and must be _indented in a block_. -There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent for all indented statements_. Functions explicitly return a value or object via the [`return`][return] keyword. ```python # Function definition on first line. def add_two_numbers(number_one, number_two): - return number_one + number_two # Returns the sum of the numbers, and is indented by 2 spaces. + return number_one + number_two # Returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 ``` + Functions that do not have an explicit `return` expression will return [`None`][none]. ```python @@ -61,93 +113,82 @@ def add_two_numbers(number_one, number_two): None ``` -Inconsistent indentation will raise an error: + +While you may choose any indentation depth, _inconsistent_ indentation in your code blocks will raise an error: ```python # The return statement line does not match the first line indent. >>> def add_three_numbers_misformatted(number_one, number_two, number_three): ... result = number_one + number_two + number_three # Indented by 4 spaces. ... return result #this was only indented by 3 spaces +... +... File "", line 3 return result ^ IndentationError: unindent does not match any outer indentation level ``` -Functions are [_called_][calls] using their name followed by `()`. -The number of arguments passed in the parentheses must match the number of parameters in the original function definition unless [default arguments][default arguments] have been used: -```python -def number_to_the_power_of(number_one, number_two): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. - """ +### Calling Functions - return number_one ** number_two +Functions are [_called_][calls] or invoked using their name followed by `()`. +The number of arguments passed in the parentheses must match the number of parameters in the original function definition. + +```python +>>> def number_to_the_power_of(number_one, number_two): + return number_one ** number_two +... >>> number_to_the_power_of(3,3) 27 ``` -A mis-match between parameters and arguments will raise an error: + +A mis-match between the number of parameters and the number of arguments will raise an error: ```python >>> number_to_the_power_of(4,) +... Traceback (most recent call last): File "", line 1, in TypeError: number_to_the_power_of() missing 1 required positional argument: 'number_two' ``` -Adding a [default value][default arguments] for a parameter can defend against such errors: -```python -def number_to_the_power_of_default(number_one, number_two=2): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. - """ - - return number_one ** number_two - ->>> number_to_the_power_of_default(4) -16 -``` - -Methods bound to class names are invoked via dot notation (.), as are functions, constants, or global names imported as part of a module.: +Calling functions defined inside a class (_methods_) use `.()`, otherwise known as dot (.) notation: ```python - -import string - -# This is a constant provided by the *string* module. ->>> print(string.ascii_lowercase) -"abcdefghijklmnopqrstuvwxyz" - -# This is a method call of the str *class*. +# This is an example of a method call of the built in str class. +# Define a variable and assign it to a string. >>> start_text = "my silly sentence for examples." + +# Uppercase the string by calling the upper method from the str class. >>> str.upper(start_text) "MY SILLY SENTENCE FOR EXAMPLES." -# This is a method call of an *instance* of the str *class*. ->>> start_text.upper() -"MY SILLY SENTENCE FOR EXAMPLES." + +# Below is an example of a method call of the built in list class. +# Define an empty list +>>> my_list = [] + +# Add an element to the list by calling the append method from the list class. +>>> my_list.append(start_text) +>>> print(my_list) +["my silly sentence for examples."] ``` + +## Comments + [Comments][comments] in Python start with a `#` that is not part of a string, and end at line termination. -Unlike many other programming languages, Python does not support multi-line comment marks. +Unlike many other programming languages, Python **does not support** multi-line comment marks. Each line of a comment block must start with the `#` character. + Comments are ignored by the interpreter: + ```python # This is a single line comment. @@ -158,26 +199,52 @@ x = "foo" # This is an in-line comment. # these should be used sparingly. ``` + +## Docstrings + The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose. -Docstrings are read by automated documentation tools and are returned by calling `.__doc__()` on the function, method, or class name. -They can also function as [lightweight unit tests][doctests], which will be covered in a later exercise. -They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][PEP257]: +Docstrings are declared using triple double quotes (""") indented at the same level as the code block: ```python -# An example on a user-defined function. -def number_to_the_power_of(number_one, number_two): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. + +# An example from PEP257 of a multi-line docstring. +def complex(real=0.0, imag=0.0): + """Form a complex number. + + Keyword arguments: + real -- the real part (default 0.0) + imag -- the imaginary part (default 0.0) """ - return number_one ** number_two + if imag == 0.0 and real == 0.0: + return complex_zero + +``` + + +Docstrings are read by automated documentation tools and are returned by calling the special attribute `.__doc__` on the function, method, or class name. +They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][pep257]. +Docstrings can also function as [lightweight unit tests][doctests], which will be covered in a later exercise. + + +```python +# An example on a user-defined function. +>>> def number_to_the_power_of(number_one, number_two): + """Raise a number to an arbitrary power. + + :param number_one: int the base number. + :param number_two: int the power to raise the base number to. + :return: int - number raised to power of second number + + Takes number_one and raises it to the power of number_two, returning the result. + """ + + return number_one ** number_two +... + +# Calling the .__doc__ attribute of the function and printing the result. >>> print(number_to_the_power_of.__doc__) Raise a number to an arbitrary power. @@ -187,7 +254,9 @@ Raise a number to an arbitrary power. Takes number_one and raises it to the power of number_two, returning the result. -# __doc__() for the built-in type: str. + + +# Printing the __doc__ attribute for the built-in type: str. >>> print(str.__doc__) str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str @@ -201,30 +270,25 @@ encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'. ``` -[PEP257]: https://www.python.org/dev/peps/pep-0257/ +[pep257]: https://www.python.org/dev/peps/pep-0257/ [assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements [calls]: https://docs.python.org/3/reference/expressions.html#calls [comments]: https://realpython.com/python-comments-guide/#python-commenting-basics -[default arguments]: https://docs.python.org/3/tutorial/controlflow.html#default-argument-values [docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings [doctests]: https://docs.python.org/3/library/doctest.html [duck typing]: https://en.wikipedia.org/wiki/Duck_typing [dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed +[everythings an object]: https://docs.python.org/3/reference/datamodel.html +[facts-and-myths-about-python-names]: https://nedbatchelder.com/text/names.html [function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing [indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation [module]: https://docs.python.org/3/tutorial/modules.html [more on functions]: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions -[naming and binding]: https://docs.python.org/3/reference/executionmodel.html#naming-and-binding [none]: https://docs.python.org/3/library/constants.html [object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming [parameters]: https://docs.python.org/3/glossary.html#term-parameter -[pep8]: https://www.python.org/dev/peps/pep-0008/ -[python docs]: https://docs.python.org/3/ [return]: https://docs.python.org/3/reference/simple_stmts.html#return -[significant indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation [snake case]: https://en.wikipedia.org/wiki/Snake_case -[the zen of python]: https://www.python.org/dev/peps/pep-0020/ [type hints]: https://docs.python.org/3/library/typing.html [variables]: https://realpython.com/python-variables/ -[what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.html diff --git a/concepts/basics/links.json b/concepts/basics/links.json index 0c7752279d..3571426010 100644 --- a/concepts/basics/links.json +++ b/concepts/basics/links.json @@ -39,10 +39,6 @@ "url": "https://realpython.com/python-variables/", "description": "variables in Python" }, - { - "url": "https://docs.python.org/3/reference/simple_stmts.html#assignment-statements", - "description": "assignment statements in Python" - }, { "url": "https://docs.python.org/3/reference/executionmodel.html#naming-and-binding", "description": "naming and binding in Python" @@ -51,10 +47,6 @@ "url": "https://docs.python.org/3/tutorial/controlflow.html#defining-functions", "description": "function definition" }, - { - "url": "https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions", - "description": "more on defining functions" - }, { "url": "https://docs.python.org/3/reference/compound_stmts.html#function", "description": "functions in Python" @@ -71,10 +63,6 @@ "url": "https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy", "description": "Pythons standard type hierarchy" }, - { - "url": "https://docs.python.org/3/reference/expressions.html#calls", - "description": "calls" - }, { "url": "https://docs.python.org/3/tutorial/controlflow.html#default-argument-values", "description": "default arguments" From dffe09ba91cefdc19bbcb5bf4be512063a53155b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 20 Feb 2023 13:58:26 -0800 Subject: [PATCH 354/826] Futher edited down links and about.md --- concepts/basics/about.md | 2 -- concepts/basics/links.json | 68 +++++++++++++++----------------------- 2 files changed, 26 insertions(+), 44 deletions(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index 70db44451f..2005266e53 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -341,12 +341,10 @@ errors defaults to 'strict'. [method objects]: https://docs.python.org/3/c-api/method.html#method-objects [module]: https://docs.python.org/3/tutorial/modules.html [more on functions]: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions -[naming and binding]: https://docs.python.org/3/reference/executionmodel.html#naming-and-binding [none]: https://docs.python.org/3/library/constants.html [object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming [objects]: https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy [parameters]: https://docs.python.org/3/glossary.html#term-parameter -[pep8]: https://www.python.org/dev/peps/pep-0008/ [peps]: https://www.python.org/dev/peps/ [psf membership]: https://www.python.org/psf/membership/ [psf]: https://www.python.org/psf/ diff --git a/concepts/basics/links.json b/concepts/basics/links.json index 3571426010..db223a4492 100644 --- a/concepts/basics/links.json +++ b/concepts/basics/links.json @@ -3,77 +3,61 @@ "url": "https://docs.python.org/3/", "description": "Python documentation" }, - { - "url": "https://www.python.org/dev/peps/pep-0020/", - "description": "The Zen of Python (PEP 20)" - }, { "url": "https://www.python.org/dev/peps/pep-0008/", "description": "PEP 8" }, { - "url": "https://www.python.org/psf-landing/", - "description": "Python Software Foundation" - }, - { - "url": "https://www.python.org/dev/peps/", - "description": "Python Enhancement Proposals or PEPs" - }, - { - "url": "https://docs.python.org/3/reference/datamodel.html", - "description": "everything in Python is an object" - }, - { - "url": "https://stackoverflow.com/questions/11328920/is-python-strongly-typed", - "description": "dynamic typing and strong typing" - }, - { - "url": "https://docs.python.org/3/library/typing.html", - "description": "type hints" + "url": "https://www.python.org/dev/peps/pep-0020/", + "description": "The Zen of Python (PEP 20)" }, { - "url": "https://docs.python.org/3/reference/lexical_analysis.html#indentation", - "description": "significant indentation" + "url": "https://nedbatchelder.com/text/names.html", + "description": "Ned Batchelder: Facts and Myths about Python Names." }, { "url": "https://realpython.com/python-variables/", "description": "variables in Python" }, - { - "url": "https://docs.python.org/3/reference/executionmodel.html#naming-and-binding", - "description": "naming and binding in Python" - }, { "url": "https://docs.python.org/3/tutorial/controlflow.html#defining-functions", "description": "function definition" }, { - "url": "https://docs.python.org/3/reference/compound_stmts.html#function", - "description": "functions in Python" + "url": "https://docs.python.org/3/tutorial/controlflow.html#default-argument-values", + "description": "default arguments" }, { - "url": "https://docs.python.org/3/reference/datamodel.html#classes", - "description": "class in Python" + "url": "https://realpython.com/python-comments-guide/#python-commenting-basics", + "description": "Comments" }, { - "url": "https://docs.python.org/3/c-api/method.html#method-objects", - "description": "methods in Python" + "url": "https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings", + "description": "docstring" }, { - "url": "https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy", - "description": "Pythons standard type hierarchy" + "url": "https://www.python.org/psf-landing/", + "description": "Python Software Foundation" }, { - "url": "https://docs.python.org/3/tutorial/controlflow.html#default-argument-values", - "description": "default arguments" + "url": "https://www.python.org/dev/peps/", + "description": "Python Enhancement Proposals or PEPs" }, { - "url": "https://realpython.com/python-comments-guide/#python-commenting-basics", - "description": "Comments" + "url": "https://docs.python.org/3/reference/datamodel.html", + "description": "everything in Python is an object" }, { - "url": "https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings", - "description": "docstring" + "url": "https://stackoverflow.com/questions/11328920/is-python-strongly-typed", + "description": "dynamic typing and strong typing" + }, + { + "url": "https://docs.python.org/3/library/typing.html", + "description": "type hints" + }, + { + "url": "https://docs.python.org/3/reference/lexical_analysis.html#indentation", + "description": "significant indentation" }, { "url": "https://docs.python.org/3/library/doctest.html", From 2dfa030c1f6c26819d515f9aa2095fe6821ca7bd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Feb 2023 14:39:52 -0800 Subject: [PATCH 355/826] Synced instrucitons and metadata from problem specs. --- exercises/practice/pascals-triangle/.docs/instructions.md | 3 +-- exercises/practice/pascals-triangle/.meta/config.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/exercises/practice/pascals-triangle/.docs/instructions.md b/exercises/practice/pascals-triangle/.docs/instructions.md index 7109334fbd..f556785931 100644 --- a/exercises/practice/pascals-triangle/.docs/instructions.md +++ b/exercises/practice/pascals-triangle/.docs/instructions.md @@ -2,8 +2,7 @@ Compute Pascal's triangle up to a given number of rows. -In Pascal's Triangle each number is computed by adding the numbers to -the right and left of the current position in the previous row. +In Pascal's Triangle each number is computed by adding the numbers to the right and left of the current position in the previous row. ```text 1 diff --git a/exercises/practice/pascals-triangle/.meta/config.json b/exercises/practice/pascals-triangle/.meta/config.json index 4793e16f1c..5871348e28 100644 --- a/exercises/practice/pascals-triangle/.meta/config.json +++ b/exercises/practice/pascals-triangle/.meta/config.json @@ -30,5 +30,5 @@ ] }, "source": "Pascal's Triangle at Wolfram Math World", - "source_url": "http://mathworld.wolfram.com/PascalsTriangle.html" + "source_url": "https://www.wolframalpha.com/input/?i=Pascal%27s+triangle" } From dc2dae98d62300317febf07a035d1cf4803271be Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Feb 2023 14:47:17 -0800 Subject: [PATCH 356/826] Synced instructions from problem specifications. --- .../practice/matching-brackets/.docs/instructions.md | 1 + exercises/practice/say/.docs/instructions.md | 10 ---------- exercises/practice/simple-cipher/.docs/instructions.md | 6 +++--- exercises/practice/space-age/.docs/instructions.md | 6 ++++++ 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/exercises/practice/matching-brackets/.docs/instructions.md b/exercises/practice/matching-brackets/.docs/instructions.md index ca7c8d838e..544daa968d 100644 --- a/exercises/practice/matching-brackets/.docs/instructions.md +++ b/exercises/practice/matching-brackets/.docs/instructions.md @@ -1,3 +1,4 @@ # Instructions Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. +The string may also contain other characters, which for the purposes of this exercise should be ignored. diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index aa2e7687fe..fb4a6dfb98 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -48,13 +48,3 @@ Put it all together to get nothing but plain English. `12345` should give `twelve thousand three hundred forty-five`. The program must also report any values that are out of range. - -### Extensions - -Use _and_ (correctly) when spelling out the number in English: - -- 14 becomes "fourteen". -- 100 becomes "one hundred". -- 120 becomes "one hundred and twenty". -- 1002 becomes "one thousand and two". -- 1323 becomes "one thousand three hundred and twenty-three". diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 9167a1d33a..475af61828 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -9,7 +9,7 @@ If anyone wishes to decipher these, and get at their meaning, he must substitute —Suetonius, Life of Julius Caesar Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. -They are vulnerable to many forms of cryptanalysis, but we are lucky that generally our little sisters are not cryptanalysts. +They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts. The Caesar Cipher was used for some messages from Julius Caesar that were sent afield. Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. @@ -29,9 +29,9 @@ When "ldpdsdqgdehdu" is put into the decode function it would return the origina ## Step 2 -Shift ciphers are no fun though when your kid sister figures it out. +Shift ciphers quickly cease to be useful when the opposition commander figures them out. +So instead, let's try using a substitution cipher. Try amending the code to allow us to specify a key and use that for the shift distance. -This is called a substitution cipher. Here's an example: diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md index 405a6dfb46..fe938cc09e 100644 --- a/exercises/practice/space-age/.docs/instructions.md +++ b/exercises/practice/space-age/.docs/instructions.md @@ -16,4 +16,10 @@ be able to say that they're 31.69 Earth-years old. If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. +Note: The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). +The Gregorian calendar has, on average, 365.2425 days. +While not entirely accurate, 365.25 is the value used in this exercise. +See [Year on Wikipedia][year] for more ways to measure a year. + [pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs +[year]: https://en.wikipedia.org/wiki/Year#Summary From a77e005f90af598e1f2fdba75e5f678c841c242c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Feb 2023 14:51:39 -0800 Subject: [PATCH 357/826] Sync metadata with problem specs. --- exercises/practice/poker/.meta/config.json | 2 +- exercises/practice/spiral-matrix/.meta/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/poker/.meta/config.json b/exercises/practice/poker/.meta/config.json index 7c4301c90b..fcb40795d6 100644 --- a/exercises/practice/poker/.meta/config.json +++ b/exercises/practice/poker/.meta/config.json @@ -28,5 +28,5 @@ }, "blurb": "Pick the best hand(s) from a list of poker hands.", "source": "Inspired by the training course from Udacity.", - "source_url": "https://www.udacity.com/course/viewer#!/c-cs212/" + "source_url": "https://www.udacity.com/course/design-of-computer-programs--cs212" } diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index 84a0d9aa4b..f6dfd5a505 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -20,7 +20,7 @@ ".meta/example.py" ] }, - "blurb": " Given the size, return a square matrix of numbers in spiral order.", + "blurb": "Given the size, return a square matrix of numbers in spiral order.", "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" } From dd115de84867893f4c2bdc1e0e35afe52555bfe0 Mon Sep 17 00:00:00 2001 From: Matias P Date: Mon, 13 Feb 2023 23:59:59 -0300 Subject: [PATCH 358/826] Adding missing double quotes One of the examples was missing double quotes. (Approach: Str translate) --- .../practice/rotational-cipher/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/rotational-cipher/.approaches/introduction.md b/exercises/practice/rotational-cipher/.approaches/introduction.md index f8c5e95ed0..1c51ac5bec 100644 --- a/exercises/practice/rotational-cipher/.approaches/introduction.md +++ b/exercises/practice/rotational-cipher/.approaches/introduction.md @@ -78,7 +78,7 @@ The benefit of this approach is that it has no visible loop, making the code mor ~~~~ ```python -AlPHABET = "abcdefghijklmnopqrstuvwxyz +AlPHABET = "abcdefghijklmnopqrstuvwxyz" def rotate(text, key): translator = AlPHABET[key:] + AlPHABET[:key] From 5656e4daa22a4fda70d70ab7544d73ac0a48b555 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 27 Feb 2023 12:23:42 +0100 Subject: [PATCH 359/826] Sync bob docs with problem-specifications The bob exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2205 --- exercises/practice/bob/.docs/instructions.md | 27 +++++++++++--------- exercises/practice/bob/.docs/introduction.md | 10 ++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 exercises/practice/bob/.docs/introduction.md diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md index 7888c9b76f..bb702f7bbe 100644 --- a/exercises/practice/bob/.docs/instructions.md +++ b/exercises/practice/bob/.docs/instructions.md @@ -1,16 +1,19 @@ # Instructions -Bob is a lackadaisical teenager. -In conversation, his responses are very limited. +Your task is to determine what Bob will reply to someone when they say something to him or ask him a question. -Bob answers 'Sure.' if you ask him a question, such as "How are you?". +Bob only ever answers one of five things: -He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals). - -He answers 'Calm down, I know what I'm doing!' if you yell a question at him. - -He says 'Fine. Be that way!' if you address him without actually saying anything. - -He answers 'Whatever.' to anything else. - -Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English. +- **"Sure."** + This is his response if you ask him a question, such as "How are you?" + The convention used for questions is that it ends with a question mark. +- **"Whoa, chill out!"** + This is his answer if you YELL AT HIM. + The convention used for yelling is ALL CAPITAL LETTERS. +- **"Calm down, I know what I'm doing!"** + This is what he says if you yell a question at him. +- **"Fine. Be that way!"** + This is how he responds to silence. + The convention used for silence is nothing, or various combinations of whitespace characters. +- **"Whatever."** + This is what he answers to anything else. diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md new file mode 100644 index 0000000000..ea4a80776b --- /dev/null +++ b/exercises/practice/bob/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Bob is a [lackadaisical][] teenager. +He likes to think that he's very cool. +And he definitely doesn't get excited about things. +That wouldn't be cool. + +When people talk to him, his responses are pretty limited. + +[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical From f0b12a42b8d28054f5b68d901103f0a21b03cb0f Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 27 Feb 2023 11:57:41 +0100 Subject: [PATCH 360/826] Sync gigasecond docs with problem-specifications The gigasecond exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2206 --- .../practice/gigasecond/.docs/instructions.md | 7 ++++-- .../practice/gigasecond/.docs/introduction.md | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 exercises/practice/gigasecond/.docs/introduction.md diff --git a/exercises/practice/gigasecond/.docs/instructions.md b/exercises/practice/gigasecond/.docs/instructions.md index 41a057c44a..1e20f0022e 100644 --- a/exercises/practice/gigasecond/.docs/instructions.md +++ b/exercises/practice/gigasecond/.docs/instructions.md @@ -1,5 +1,8 @@ # Instructions -Given a moment, determine the moment that would be after a gigasecond has passed. +Your task is to determine the date and time one gigasecond after a certain date. -A gigasecond is 10^9 (1,000,000,000) seconds. +A gigasecond is one thousand million seconds. +That is a one with nine zeros after it. + +If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md new file mode 100644 index 0000000000..74afaa994f --- /dev/null +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction + +The way we measure time is kind of messy. +We have 60 seconds in a minute, and 60 minutes in an hour. +This comes from ancient Babylon, where they used 60 as the basis for their number system. +We have 24 hours in a day, 7 days in a week, and how many days in a month? +Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. + +What if, instead, we only use seconds to express time intervals? +Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. + +- A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). +- Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). +- And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. + +```exercism/note +If we ever colonize Mars or some other planet, measuring time is going to get even messier. +If someone says "year" do they mean a year on Earth or a year on Mars? + +The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. +In it the author uses the metric system as the basis for time measurements. + +[vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ +``` From eaabc0da0921cace8b2f202ff46a81360a0e2afd Mon Sep 17 00:00:00 2001 From: Cedd Burge Date: Thu, 2 Mar 2023 13:15:45 +0000 Subject: [PATCH 361/826] Remove TODO comments Most of the other concept exercises up to this one communicate the requirement to implement the functions by using docstrings and `pass`, so I think it makes sense to be consistent and do it here as well. --- exercises/concept/locomotive-engineer/locomotive_engineer.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer.py b/exercises/concept/locomotive-engineer/locomotive_engineer.py index d9291f65a3..cea48cc028 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer.py @@ -1,7 +1,6 @@ """Functions which helps the locomotive engineer to keep track of the train.""" -# TODO: define the 'get_list_of_wagons' function def get_list_of_wagons(): """Return a list of wagons. @@ -11,7 +10,6 @@ def get_list_of_wagons(): pass -# TODO: define the 'fixListOfWagons()' function def fix_list_of_wagons(each_wagons_id, missing_wagons): """Fix the list of wagons. @@ -22,7 +20,6 @@ def fix_list_of_wagons(each_wagons_id, missing_wagons): pass -# TODO: define the 'add_missing_stops()' function def add_missing_stops(): """Add missing stops to route dict. @@ -33,7 +30,6 @@ def add_missing_stops(): pass -# TODO: define the 'extend_route_information()' function def extend_route_information(route, more_route_information): """Extend route information with more_route_information. @@ -44,7 +40,6 @@ def extend_route_information(route, more_route_information): pass -# TODO: define the 'fix_wagon_depot()' function def fix_wagon_depot(wagons_rows): """Fix the list of rows of wagons. From a769aeb33438567a0229e30ab98a51d5f0236340 Mon Sep 17 00:00:00 2001 From: Cedd Burge Date: Thu, 2 Mar 2023 11:56:00 +0000 Subject: [PATCH 362/826] Correct rounding explanation Fractional parts of 0.5 and above round up, whereas the documentation had examples of 0.5 rounding both up and down! --- exercises/concept/making-the-grade/.docs/instructions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/concept/making-the-grade/.docs/instructions.md b/exercises/concept/making-the-grade/.docs/instructions.md index 18e5c0d908..4fac63889b 100644 --- a/exercises/concept/making-the-grade/.docs/instructions.md +++ b/exercises/concept/making-the-grade/.docs/instructions.md @@ -9,7 +9,8 @@ You decide to make things a little more interesting by putting together some fun While you can give "partial credit" on exam questions, overall exam scores have to be `int`s. So before you can do anything else with the class scores, you need to go through the grades and turn any `float` scores into `int`s. Lucky for you, Python has the built-in [`round()`][round] function you can use. -A score of 75.45 or 75.49 will round to 75. A score of 40.50 will round to 40. A score of 43.50 (_or above_) will round to 44. +The round function will round down fractional parts less than 0.5, and round up fractional parts greater than or equal to 0.5. +For example, a score of 75.45 or 75.49 will round to 75, and score a of 75.50 or 75.64 will round to 76. There shouldn't be any scores that have more than two places after the decimal point. Create the function `round_scores()` that takes a `list` of `student_scores`. From 79a7dbe013890efafdb4d9eefad413f477db9885 Mon Sep 17 00:00:00 2001 From: Cedd Burge Date: Fri, 3 Mar 2023 10:14:32 +0000 Subject: [PATCH 363/826] Remove the explanation of `round` --- exercises/concept/making-the-grade/.docs/instructions.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/exercises/concept/making-the-grade/.docs/instructions.md b/exercises/concept/making-the-grade/.docs/instructions.md index 4fac63889b..43b25420c0 100644 --- a/exercises/concept/making-the-grade/.docs/instructions.md +++ b/exercises/concept/making-the-grade/.docs/instructions.md @@ -9,10 +9,6 @@ You decide to make things a little more interesting by putting together some fun While you can give "partial credit" on exam questions, overall exam scores have to be `int`s. So before you can do anything else with the class scores, you need to go through the grades and turn any `float` scores into `int`s. Lucky for you, Python has the built-in [`round()`][round] function you can use. -The round function will round down fractional parts less than 0.5, and round up fractional parts greater than or equal to 0.5. -For example, a score of 75.45 or 75.49 will round to 75, and score a of 75.50 or 75.64 will round to 76. -There shouldn't be any scores that have more than two places after the decimal point. - Create the function `round_scores()` that takes a `list` of `student_scores`. This function should _consume_ the input `list` and `return` a new list with all the scores converted to `int`s. The order of the scores in the resulting `list` is not important. From 0191253ea08036a07353778bb6a392d5bc2645b8 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Fri, 3 Mar 2023 12:23:12 +0100 Subject: [PATCH 364/826] Sync two-fer docs with problem-specifications The two-fer exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2204 --- exercises/practice/two-fer/.docs/instructions.md | 16 +++++++--------- exercises/practice/two-fer/.docs/introduction.md | 8 ++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 exercises/practice/two-fer/.docs/introduction.md diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md index bdd72bde11..a9bb4a3cd3 100644 --- a/exercises/practice/two-fer/.docs/instructions.md +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -1,17 +1,15 @@ # Instructions -`Two-fer` or `2-fer` is short for two for one. -One for you and one for me. +Your task is to determine what you will say as you give away the extra cookie. -Given a name, return a string with the message: +If your friend likes cookies, and is named Do-yun, then you will say: ```text -One for name, one for me. +One for Do-yun, one for me. ``` -Where "name" is the given name. - -However, if the name is missing, return the string: +If your friend doesn't like cookies, you give the cookie to the next person in line at the bakery. +Since you don't know their name, you will say _you_ instead. ```text One for you, one for me. @@ -19,9 +17,9 @@ One for you, one for me. Here are some examples: -|Name |String to return +|Name |Dialogue |:-------|:------------------ |Alice |One for Alice, one for me. -|Bob |One for Bob, one for me. +|Bohdan |One for Bohdan, one for me. | |One for you, one for me. |Zaphod |One for Zaphod, one for me. diff --git a/exercises/practice/two-fer/.docs/introduction.md b/exercises/practice/two-fer/.docs/introduction.md new file mode 100644 index 0000000000..8c124394aa --- /dev/null +++ b/exercises/practice/two-fer/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +In some English accents, when you say "two for" quickly, it sounds like "two fer". +Two-for-one is a way of saying that if you buy one, you also get one for free. +So the phrase "two-fer" often implies a two-for-one offer. + +Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). +You go for the offer and (very generously) decide to give the extra cookie to a friend. From 90efc4cdbaa2334e0bb3bea22c8d834825dbddf8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 24 Feb 2023 23:40:55 -0800 Subject: [PATCH 365/826] Modified error messages for hello world to be more direct and clearer. --- .../practice/hello-world/.meta/template.j2 | 26 ++++++++++++++--- exercises/practice/hello-world/hello_world.py | 4 +-- .../practice/hello-world/hello_world_test.py | 28 +++++++++++++------ 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/exercises/practice/hello-world/.meta/template.j2 b/exercises/practice/hello-world/.meta/template.j2 index 7c5e35bfcc..dbcde585e9 100644 --- a/exercises/practice/hello-world/.meta/template.j2 +++ b/exercises/practice/hello-world/.meta/template.j2 @@ -1,10 +1,28 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} + +import unittest + +try: + from hello_world import ( + hello, + ) + +except ImportError as import_fail: + message = import_fail.args[0].split('(', maxsplit=1) + item_name = import_fail.args[0].split()[3] + + item_name = item_name[:-1] + "()'" + + # pylint: disable=raise-missing-from + raise ImportError("\n\nMISSING FUNCTION --> In your 'hello_world.py' file, we can not find or import the" + f' function named {item_name}. \nThe tests for this first exercise expect a function that' + f' returns the string "Hello, World!"' + f'\n\nDid you use print("Hello, World!") instead?') from None + class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} def test_{{ case["description"] | to_snake }}(self): - self.assertEqual({{ case["property"] }}(), "{{ case["expected"] }}") + msg = "\n\nThis test expects a return of the string 'Hello, World!' \nDid you use print('Hello, World!') by mistake?" + self.assertEqual({{ case["property"] }}(), "{{ case["expected"] }}", msg=msg) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/hello-world/hello_world.py b/exercises/practice/hello-world/hello_world.py index adaa6c2c99..e14d2b47a6 100644 --- a/exercises/practice/hello-world/hello_world.py +++ b/exercises/practice/hello-world/hello_world.py @@ -1,2 +1,2 @@ -def hello(): - return 'Goodbye, Mars!' +print('Hello, World!') + #'Goodbye, Mars!' diff --git a/exercises/practice/hello-world/hello_world_test.py b/exercises/practice/hello-world/hello_world_test.py index 39c9612482..245f5434fa 100644 --- a/exercises/practice/hello-world/hello_world_test.py +++ b/exercises/practice/hello-world/hello_world_test.py @@ -1,16 +1,26 @@ import unittest -from hello_world import ( - hello, -) +try: + from hello_world import ( + hello, + ) -# Tests adapted from `problem-specifications//canonical-data.json` +except ImportError as import_fail: + message = import_fail.args[0].split("(", maxsplit=1) + item_name = import_fail.args[0].split()[3] + item_name = item_name[:-1] + "()'" -class HelloWorldTest(unittest.TestCase): - def test_say_hi(self): - self.assertEqual(hello(), "Hello, World!") + # pylint: disable=raise-missing-from + raise ImportError( + "\n\nMISSING FUNCTION --> In your 'hello_world.py' file, we can not find or import the" + f" function named {item_name}. \nThe tests for this first exercise expect a function that" + f' returns the string "Hello, World!"' + f'\n\nDid you use print("Hello, World!") instead?' + ) from None -if __name__ == "__main__": - unittest.main() +class HelloWorldTest(unittest.TestCase): + def test_say_hi(self): + msg = "\n\nThis test expects a return of the string 'Hello, World!' \nDid you use print('Hello, World!') by mistake?" + self.assertEqual(hello(), "Hello, World!", msg=msg) From df98c97ecbcc01971830a6d25e2469dc86cf8ac2 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 24 Feb 2023 23:45:13 -0800 Subject: [PATCH 366/826] Backed out changes made for testing. --- exercises/practice/hello-world/hello_world.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/hello-world/hello_world.py b/exercises/practice/hello-world/hello_world.py index e14d2b47a6..adaa6c2c99 100644 --- a/exercises/practice/hello-world/hello_world.py +++ b/exercises/practice/hello-world/hello_world.py @@ -1,2 +1,2 @@ -print('Hello, World!') - #'Goodbye, Mars!' +def hello(): + return 'Goodbye, Mars!' From 5eb8694e9a69c88314d601c0b2edd7d77fd7a178 Mon Sep 17 00:00:00 2001 From: Matthijs Blom <19817960+MatthijsBlom@users.noreply.github.com> Date: Sun, 5 Mar 2023 14:55:05 +0100 Subject: [PATCH 367/826] Correct arithmetic example --- concepts/numbers/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index 5171b69354..d5a4f7271c 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -167,7 +167,7 @@ This means calculations within `()` have the highest priority, followed by `**`, -11 >>> (2 + 3 - 4) * 4 -20 +4 # In the following example, the `**` operator has the highest priority, then `*`, then `+` # Meaning we first do 4 ** 4, then 3 * 256, then 2 + 768 From a308ebb04d70a68a46466a960d3563442811aa2c Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:03:49 +0100 Subject: [PATCH 368/826] Sync pangram docs with problem-specifications The pangram exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2215 ---- If you approve this pull request, I will eventually merge it. However, if you are happy with this change **please merge the pull request**, as it will get the changes into the hands of the students much more quickly. --- exercises/practice/pangram/.docs/instructions.md | 9 ++++----- exercises/practice/pangram/.docs/introduction.md | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/pangram/.docs/introduction.md diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index de83d54eb6..d5698bc2a2 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Determine if a sentence is a pangram. -A pangram (Greek: παν γράμμα, pan gramma, "every letter") is a sentence using every letter of the alphabet at least once. -The best known English pangram is: +Your task is to figure out if a sentence is a pangram. -> The quick brown fox jumps over the lazy dog. +A pangram is a sentence using every letter of the alphabet at least once. +It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). -The alphabet used consists of letters `a` to `z`, inclusive, and is case insensitive. +For this exercise we only use the basic letters used in the English alphabet: `a` to `z`. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md new file mode 100644 index 0000000000..d38fa341df --- /dev/null +++ b/exercises/practice/pangram/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that sells fonts through their website. +They'd like to show a different sentence each time someone views a font on their website. +To give a comprehensive sense of the font, the random sentences should use **all** the letters in the English alphabet. + +They're running a competition to get suggestions for sentences that they can use. +You're in charge of checking the submissions to see if they are valid. + +```exercism/note +Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". + +The best known English pangram is: + +> The quick brown fox jumps over the lazy dog. +``` From 306dfe2d3cfc109988f845050409aa0a3c0ede47 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 25 Feb 2023 22:50:33 -0800 Subject: [PATCH 369/826] And yet more edits to this exercise and concept. --- concepts/basics/about.md | 190 +++++--------- concepts/basics/introduction.md | 233 +++--------------- .../.docs/introduction.md | 183 ++++---------- .../guidos-gorgeous-lasagna/analysis.json | 33 +++ .../guidos-gorgeous-lasagna/lasagna.py | 35 +-- 5 files changed, 197 insertions(+), 477 deletions(-) create mode 100644 exercises/concept/guidos-gorgeous-lasagna/analysis.json diff --git a/concepts/basics/about.md b/concepts/basics/about.md index 2005266e53..96ac4ec256 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -2,13 +2,10 @@ Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. -Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally [everything in Python is an object][everythings an object]. +Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**. Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. - -The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies - Python was created by Guido van Rossum and first released in 1991. The [Python Software Foundation][psf] manages and directs resources for Python and CPython development and receives proposals for changes to the language from [members][psf membership] of the community via [Python Enhancement Proposals or PEPs][peps]. @@ -21,6 +18,7 @@ Complete documentation for the current release can be found at [docs.python.org] - [Python FAQs][python faqs] - [Python Glossary of Terms][python glossary of terms] + This concept introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. @@ -28,44 +26,60 @@ This concept introduces 4 major Python language features: Name Assignment (_vari In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names. -~~~~ +The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies. + +On the Python track, [variables][variables] are always written in [`snake_case`][snake case], and constants in `SCREAMING_SNAKE_CASE` -## Name Assignment and Re-assignment +[snake case]: https://en.wikipedia.org/wiki/Snake_case +[variables]: https://realpython.com/python-variables/ +[what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.html +[the zen of python]: https://www.python.org/dev/peps/pep-0020/ +~~~~ -In Python, there are no keywords to define variables or constants. -Both are [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. -On Exercism, [variables][variables] are always written in [`snake_case`][snake case], and _constants_ in `SCREAMING_SNAKE_CASE`. -Names are assigned to values using `=`, or the [_assignment operator_][assignment statements] (` = `). -A name (_variable or constant_) can be re-assigned over its lifetime to different values/object types. +## Name Assignment (Variables & Constants) + +In Python, there are no keywords used in creating variables or constants. +Instead, programmer defined [_names_][facts-and-myths-about-python-names] (also called _variables__) can be bound to any type of object using the assignment `=` operator: ` = `. +A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment: + ```python -# Assigning my_first_variable to a numeric value. ->>> my_first_variable = 1 +>>> my_first_variable = 1 # Name bound to an integer object of value one. +>>> my_first_variable = 2 # Name re-assigned to integer value 2. + >>> print(type(my_first_variable)) >>> print(my_first_variable) -1 +2 -# Reassigning my_first_variable to a new string value. ->>> my_first_variable = "Now, I'm a string." +>>> my_first_variable = "Now, I'm a string." # You may re-bind a name to a different object type and value. >>> print(type(my_first_variable)) >>> print(my_first_variable) -"Now, I'm a string." +"Now, I'm a string." # Strings can be declared using single or double quote marks. + +import collections +>>> my_first_variable = collections.Counter([1,1,2,3,3,3,4,5,6,7]) +>>> print(type(my_first_variable)) + + +>>> print(my_first_variable) +>>> Counter({3: 3, 1: 2, 2: 1, 4: 1, 5: 1, 6: 1, 7: 1}) ``` ### Constants -Constants are typically defined at a [module][module] level, being values that are accessible outside function or class scope. -Constant names **_can be reassigned to new values_**, but they are _intended_ to be named only once. -Using `SCREAMING_SNAKE_CASE` warns other programmers that these names should not be mutated or reassigned. +Constants are names meant to be assigned only once in a program. +They should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in the program. +Using `SCREAMING_SNAKE_CASE` signals that the name should not be re-assigned, or its value mutated. + ```python # All caps signal that this is intended as a constant. @@ -73,7 +87,8 @@ MY_FIRST_CONSTANT = 16 # Re-assignment will be allowed by the compiler & interpreter, # but this is VERY strongly discouraged. -# Please don't: MY_FIRST_CONSTANT = "Some other value" +# Please don't do this, it could create problems in your program! +MY_FIRST_CONSTANT = "Some other value" ``` @@ -85,49 +100,45 @@ Functions can be executed by themselves, passed as arguments to other functions, When functions are bound to a [class][classes] name, they're referred to as [methods][method objects]. Related functions and classes (_with their methods_) can be grouped together in the same file or module, and imported in part or in whole for use in other programs. -The keyword `def` begins a [function definition][function definition]. -It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. -Parameters can be of several different varieties, and can even [vary][more on functions] in length. - -The `def` line is terminated with a colon (`:`): - -```python -# Function definition. -def my_function_name(parameter, second_parameter): - - -``` - -Statements for the `function body` begin on the line following `def` and must be _indented in a block_. -There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent_ for all indented statements. +The `def` keyword begins a [function definition][function definition]. +Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon. +Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. ```python -# Function definition on first line. +# The body of this function is indented by 2 spaces, & prints the sum of the numbers def add_two_numbers(number_one, number_two): - print(number_one + number_two) # Prints the sum of the numbers, and is indented by 2 spaces. + total = number_one + number_two + print(total) >>> add_two_numbers(3, 4) 7 -``` +# Inconsistent indentation in your code blocks will raise an error. +>>> def add_three_numbers_misformatted(number_one, number_two, number_three): +... result = number_one + number_two + number_three # This was indented by 4 spaces. +... print(result) #this was only indented by 3 spaces +... +... + File "", line 3 + print(result) + ^ +IndentationError: unindent does not match any outer indentation level +``` + Functions explicitly return a value or object via the [`return`][return] keyword. +Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none]. ```python # Function definition on first line. def add_two_numbers(number_one, number_two): - return number_one + number_two # Returns the sum of the numbers. + result = number_one + number_two + return result # Returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -``` - - - -Functions that do not have an explicit `return` expression will return [`None`][none]. -```python # This function will return None. def add_two_numbers(number_one, number_two): result = number_one + number_two @@ -136,98 +147,39 @@ def add_two_numbers(number_one, number_two): None ``` -While you may choose any indentation depth, _inconsistent_ indentation in your code blocks will raise an error: - -```python -# The return statement line does not match the first line indent. ->>> def add_three_numbers_misformatted(number_one, number_two, number_three): -... result = number_one + number_two + number_three # Indented by 4 spaces. -... return result #this was only indented by 3 spaces -... -... - File "", line 3 - return result - ^ -IndentationError: unindent does not match any outer indentation level -``` - ### Calling Functions -Functions are [_called_][calls] using their name followed by `()`. -The number of arguments passed in the parentheses must match the number of parameters in the original function definition unless [default arguments][default arguments] have been used. +Functions are [_called_][calls] or invoked using their name followed by `()`. +Dot (`.`) notation is used for calling functions defined inside a class or module. ```python >>> def number_to_the_power_of(number_one, number_two): return number_one ** number_two ... ->>> number_to_the_power_of(3,3) +>>> number_to_the_power_of(3,3) # Invoking the function with the arguments 3 and 3. 27 -``` - -A mis-match between the number of parameters and the number of arguments will raise an error: -```python +# A mis-match between the number of parameters and the number of arguments will raise an error. >>> number_to_the_power_of(4,) ... Traceback (most recent call last): File "", line 1, in TypeError: number_to_the_power_of() missing 1 required positional argument: 'number_two' -``` - -Adding a [default value][default arguments] for a parameter can defend against such errors: - -```python -# Note the default value of 2 assigned below. -def number_to_the_power_of_default(number_one, number_two=2): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. - """ - - return number_one ** number_two - -# Because there was a default value, this call with only one argument does not throw an error. ->>> number_to_the_power_of_default(4) -16 -``` - - -Methods bound to class names are invoked via dot notation (`.())`, as are functions (`.()`), constants (`.`), or any other global names imported as part of a module.: - -```python -# This is an example of a method call of the built in str class. -# Define a variable and assign it to a string. +# Calling methods or functions in classes and modules. >>> start_text = "my silly sentence for examples." - -# Uppercase the string by calling the upper method from the str class. ->>> str.upper(start_text) +>>> str.upper(start_text) # Calling the upper() method for the built-in str class. "MY SILLY SENTENCE FOR EXAMPLES." +# Importing the math module +import math -# Below is an example of a method call of the built in list class. -# Define an empty list ->>> my_list = [] - -# Add an element to the list by calling the append method from the list class. ->>> my_list.append(start_text) ->>> print(my_list) -["my silly sentence for examples."] - -import string - -# This is a constant provided by the *string* module. ->>> alphabet = string.ascii_lowercase ->>> print(alphabet) -"abcdefghijklmnopqrstuvwxyz" +>>> math.pow(2,4) # Calling the pow() function from the math module +>>> 16.0 ``` @@ -323,11 +275,9 @@ errors defaults to 'strict'. ``` [PEP257]: https://www.python.org/dev/peps/pep-0257/ -[assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements [calls]: https://docs.python.org/3/reference/expressions.html#calls [classes]: https://docs.python.org/3/reference/datamodel.html#classes [comments]: https://realpython.com/python-comments-guide/#python-commenting-basics -[default arguments]: https://docs.python.org/3/tutorial/controlflow.html#default-argument-values [docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings [doctests]: https://docs.python.org/3/library/doctest.html [duck typing]: https://en.wikipedia.org/wiki/Duck_typing @@ -337,10 +287,8 @@ errors defaults to 'strict'. [function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [functions]: https://docs.python.org/3/reference/compound_stmts.html#function [gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing -[indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation [method objects]: https://docs.python.org/3/c-api/method.html#method-objects [module]: https://docs.python.org/3/tutorial/modules.html -[more on functions]: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions [none]: https://docs.python.org/3/library/constants.html [object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming [objects]: https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy @@ -357,9 +305,5 @@ errors defaults to 'strict'. [python tutorial]: https://docs.python.org/3/tutorial/index.html [return]: https://docs.python.org/3/reference/simple_stmts.html#return [significant indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation -[snake case]: https://en.wikipedia.org/wiki/Snake_case -[the zen of python]: https://www.python.org/dev/peps/pep-0020/ [turtles all the way down]: https://en.wikipedia.org/wiki/Turtles_all_the_way_down [type hints]: https://docs.python.org/3/library/typing.html -[variables]: https://realpython.com/python-variables/ -[what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.html diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index ef002e7cd3..8c5447f181 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -1,110 +1,85 @@ # Introduction -Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. -It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. -Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally [everything in Python is an object][everythings an object]. +Python is a [dynamic and strongly][dynamic typing in python] typed programming language. +It employs both [duck typing][duck typing] and [gradual typing][gradual typing] via [type hints][type hints]. -This concept introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. +While Python supports many different programming _styles_, internally **everything in Python is an [object][everythings an object]**. +This includes numbers, strings, lists, and even functions. -~~~~exercism/note +## Name Assignment (Variables & Constants) -In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names. +Programmer defined [_names_][facts-and-myths-about-python-names] (also called _variables__) can be bound to any type of object using the assignment `=` operator: ` = `. +A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. -~~~~ - - -## Name Assignment and Re-assignment - -In Python, there are no keywords to define variables or constants. -Both are [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. -On Exercism, [variables][variables] are always written in [`snake_case`][snake case], and _constants_ in `SCREAMING_SNAKE_CASE`. - -Names are assigned to values using `=`, or the [_assignment operator_][assignment statements] (` = `). -A name (_variable or constant_) can be re-assigned over its lifetime to different values/object types. - -For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment: ```python -# Assigning my_first_variable to a numeric value. ->>> my_first_variable = 1 +>>> my_first_variable = 1 # Name bound to an integer object of value one. +>>> my_first_variable = 2 # Name re-assigned to integer value 2. + >>> print(type(my_first_variable)) >>> print(my_first_variable) -1 +2 -# Reassigning my_first_variable to a new string value. ->>> my_first_variable = "Now, I'm a string." +>>> my_first_variable = "Now, I'm a string." # You may re-bind a name to a different object type and value. >>> print(type(my_first_variable)) >>> print(my_first_variable) -"Now, I'm a string." +"Now, I'm a string." # Strings can be declared using single or double quote marks. ``` ### Constants -Constants are typically defined at a [module][module] level, being values that are accessible outside function or class scope. -Constant names **_can be reassigned to new values_**, but they are _intended_ to be named only once. -Using `SCREAMING_SNAKE_CASE` warns other programmers that these names should not be mutated or reassigned. - -```python -# All caps signal that this is intended as a constant. -MY_FIRST_CONSTANT = 16 - -# Re-assignment will be allowed by the compiler & interpreter, -# but this is VERY strongly discouraged. -# Please don't: MY_FIRST_CONSTANT = "Some other value" -``` +Constants are names meant to be assigned only once in a program. +They should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in the program. +Using `SCREAMING_SNAKE_CASE` signals that the name should not be re-assigned, or its value mutated. ## Functions -The keyword `def` begins a [function definition][function definition]. -It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. -Parameters can be of several different varieties, and can even [vary][more on functions] in length. - -The `def` line is terminated with a colon: - -```python -# Function definition. -def my_function_name(parameter, second_parameter): - - -``` - - +The `def` keyword begins a [function definition][function definition]. +Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon. Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. -There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent_ for all indented statements. ```python -# Function definition on first line. +# The body of this function is indented by 2 spaces, & prints the sum of the numbers def add_two_numbers(number_one, number_two): - print(number_one + number_two) # Prints the sum of the numbers, and is indented by 2 spaces. + total = number_one + number_two + print(total) >>> add_two_numbers(3, 4) 7 -``` +# Inconsistent indentation in your code blocks will raise an error. +>>> def add_three_numbers_misformatted(number_one, number_two, number_three): +... result = number_one + number_two + number_three # This was indented by 4 spaces. +... print(result) #this was only indented by 3 spaces +... +... + File "", line 3 + print(result) + ^ +IndentationError: unindent does not match any outer indentation level +``` + Functions explicitly return a value or object via the [`return`][return] keyword. +Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none]. ```python # Function definition on first line. def add_two_numbers(number_one, number_two): - return number_one + number_two # Returns the sum of the numbers. + result = number_one + number_two + return result # Returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -``` - -Functions that do not have an explicit `return` expression will return [`None`][none]. - -```python # This function will return None. def add_two_numbers(number_one, number_two): result = number_one + number_two @@ -114,95 +89,17 @@ None ``` -While you may choose any indentation depth, _inconsistent_ indentation in your code blocks will raise an error: - -```python -# The return statement line does not match the first line indent. ->>> def add_three_numbers_misformatted(number_one, number_two, number_three): -... result = number_one + number_two + number_three # Indented by 4 spaces. -... return result #this was only indented by 3 spaces -... -... - File "", line 3 - return result - ^ -IndentationError: unindent does not match any outer indentation level -``` - - -### Calling Functions - -Functions are [_called_][calls] or invoked using their name followed by `()`. -The number of arguments passed in the parentheses must match the number of parameters in the original function definition. - -```python ->>> def number_to_the_power_of(number_one, number_two): - return number_one ** number_two -... - ->>> number_to_the_power_of(3,3) -27 -``` - - -A mis-match between the number of parameters and the number of arguments will raise an error: - -```python ->>> number_to_the_power_of(4,) -... -Traceback (most recent call last): - File "", line 1, in -TypeError: number_to_the_power_of() missing 1 required positional argument: 'number_two' - -``` - - -Calling functions defined inside a class (_methods_) use `.()`, otherwise known as dot (.) notation: - -```python -# This is an example of a method call of the built in str class. -# Define a variable and assign it to a string. ->>> start_text = "my silly sentence for examples." - -# Uppercase the string by calling the upper method from the str class. ->>> str.upper(start_text) -"MY SILLY SENTENCE FOR EXAMPLES." - - -# Below is an example of a method call of the built in list class. -# Define an empty list ->>> my_list = [] - -# Add an element to the list by calling the append method from the list class. ->>> my_list.append(start_text) ->>> print(my_list) -["my silly sentence for examples."] -``` - - ## Comments [Comments][comments] in Python start with a `#` that is not part of a string, and end at line termination. Unlike many other programming languages, Python **does not support** multi-line comment marks. Each line of a comment block must start with the `#` character. -Comments are ignored by the interpreter: - - -```python -# This is a single line comment. - -x = "foo" # This is an in-line comment. - -# This is a multi-line -# comment block over multiple lines -- -# these should be used sparingly. -``` - ## Docstrings The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose. +Docstring conventions are laid out in [PEP257][pep257]. Docstrings are declared using triple double quotes (""") indented at the same level as the code block: @@ -222,73 +119,17 @@ def complex(real=0.0, imag=0.0): ``` - -Docstrings are read by automated documentation tools and are returned by calling the special attribute `.__doc__` on the function, method, or class name. -They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][pep257]. - -Docstrings can also function as [lightweight unit tests][doctests], which will be covered in a later exercise. - - -```python -# An example on a user-defined function. ->>> def number_to_the_power_of(number_one, number_two): - """Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. - """ - - return number_one ** number_two -... - -# Calling the .__doc__ attribute of the function and printing the result. ->>> print(number_to_the_power_of.__doc__) -Raise a number to an arbitrary power. - - :param number_one: int the base number. - :param number_two: int the power to raise the base number to. - :return: int - number raised to power of second number - - Takes number_one and raises it to the power of number_two, returning the result. - - - -# Printing the __doc__ attribute for the built-in type: str. ->>> print(str.__doc__) -str(object='') -> str -str(bytes_or_buffer[, encoding[, errors]]) -> str - -Create a new string object from the given object. If encoding or -errors is specified, then the object must expose a data buffer -that will be decoded using the given encoding and error handler. -Otherwise, returns the result of object.__str__() (if defined) -or repr(object). -encoding defaults to sys.getdefaultencoding(). -errors defaults to 'strict'. -``` - [pep257]: https://www.python.org/dev/peps/pep-0257/ -[assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements -[calls]: https://docs.python.org/3/reference/expressions.html#calls [comments]: https://realpython.com/python-comments-guide/#python-commenting-basics [docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings -[doctests]: https://docs.python.org/3/library/doctest.html [duck typing]: https://en.wikipedia.org/wiki/Duck_typing [dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed [everythings an object]: https://docs.python.org/3/reference/datamodel.html [facts-and-myths-about-python-names]: https://nedbatchelder.com/text/names.html [function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing -[indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation [module]: https://docs.python.org/3/tutorial/modules.html -[more on functions]: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions [none]: https://docs.python.org/3/library/constants.html -[object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming [parameters]: https://docs.python.org/3/glossary.html#term-parameter [return]: https://docs.python.org/3/reference/simple_stmts.html#return -[snake case]: https://en.wikipedia.org/wiki/Snake_case [type hints]: https://docs.python.org/3/library/typing.html -[variables]: https://realpython.com/python-variables/ diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index 5f4ab830bc..82002c3ac2 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -1,110 +1,100 @@ # Introduction -Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. -It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. -Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally [everything in Python is an object][everythings an object]. +Python is a [dynamic and strongly][dynamic typing in python] typed programming language. +It employs both [duck typing][duck typing] and [gradual typing][gradual typing] via [type hints][type hints]. -This exercise introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. +While Python supports many different programming _styles_, internally **everything in Python is an [object][everythings an object]**. +This includes numbers, strings, lists, and even functions. + +We'll dig more into what all of that means as we continue through the track. + +This first exercise introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. ~~~~exercism/note In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names. -~~~~ +On the Python track, [variables][variables] are always written in [`snake_case`][snake case], and constants in `SCREAMING_SNAKE_CASE` +[variables]: https://realpython.com/python-variables/ +[snake case]: https://en.wikipedia.org/wiki/Snake_case +~~~~ -## Name Assignment and Re-assignment -In Python, there are no keywords to define variables or constants. -Both are [_names_][facts-and-myths-about-python-names] that help programmers reference values (_objects_) in a program and are written differently only by convention. -On Exercism, [variables][variables] are always written in [`snake_case`][snake case], and _constants_ in `SCREAMING_SNAKE_CASE`. +## Name Assignment (Variables & Constants) -Names are assigned to values using `=`, or the [_assignment operator_][assignment statements] (` = `). -A name (_variable or constant_) can be re-assigned over its lifetime to different values/object types. +Programmer defined [_names_][facts-and-myths-about-python-names] (also called _variables__) can be bound to any type of object using the assignment `=` operator: ` = `. +A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. -For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment: ```python -# Assigning my_first_variable to a numeric value. ->>> my_first_variable = 1 +>>> my_first_variable = 1 # Name bound to an integer object of value one. +>>> my_first_variable = 2 # Name re-assigned to integer value 2. + >>> print(type(my_first_variable)) >>> print(my_first_variable) -1 +2 -# Reassigning my_first_variable to a new string value. ->>> my_first_variable = "Now, I'm a string." +>>> my_first_variable = "Now, I'm a string." # You may re-bind a name to a different object type and value. >>> print(type(my_first_variable)) >>> print(my_first_variable) -"Now, I'm a string." +"Now, I'm a string." # Strings can be declared using single or double quote marks. ``` ### Constants -Constants are typically defined at a [module][module] level, being values that are accessible outside function or class scope. -Constant names **_can be reassigned to new values_**, but they are _intended_ to be named only once. -Using `SCREAMING_SNAKE_CASE` warns other programmers that these names should not be mutated or reassigned. - -```python -# All caps signal that this is intended as a constant. -MY_FIRST_CONSTANT = 16 - -# Re-assignment will be allowed by the compiler & interpreter, -# but this is VERY strongly discouraged. -# Please don't: MY_FIRST_CONSTANT = "Some other value" -``` +Constants are names meant to be assigned only once in a program. +They should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in the program. +Using `SCREAMING_SNAKE_CASE` signals that the name should not be re-assigned, or its value mutated. ## Functions -The keyword `def` begins a [function definition][function definition]. -It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters]. -Parameters can be of several different varieties, and can even [vary][more on functions] in length. - -The `def` line is terminated with a colon: - -```python -# Function definition. -def my_function_name(parameter, second_parameter): - - -``` - - +The `def` keyword begins a [function definition][function definition]. +Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon. Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. -There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent_ for all indented statements. ```python -# Function definition on first line. +# The body of this function is indented by 2 spaces, & prints the sum of the numbers def add_two_numbers(number_one, number_two): - print(number_one + number_two) # Prints the sum of the numbers, and is indented by 2 spaces. + total = number_one + number_two + print(total) >>> add_two_numbers(3, 4) 7 -``` +# Inconsistent indentation in your code blocks will raise an error. +>>> def add_three_numbers_misformatted(number_one, number_two, number_three): +... result = number_one + number_two + number_three # This was indented by 4 spaces. +... print(result) #this was only indented by 3 spaces +... +... + File "", line 3 + print(result) + ^ +IndentationError: unindent does not match any outer indentation level +``` + Functions explicitly return a value or object via the [`return`][return] keyword. +Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none]. ```python # Function definition on first line. def add_two_numbers(number_one, number_two): - return number_one + number_two # Returns the sum of the numbers. + result = number_one + number_two + return result # Returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -``` - -Functions that do not have an explicit `return` expression will return [`None`][none]. - -```python # This function will return None. def add_two_numbers(number_one, number_two): result = number_one + number_two @@ -114,69 +104,38 @@ None ``` -While you may choose any indentation depth, _inconsistent_ indentation in your code blocks will raise an error: - -```python -# The return statement line does not match the first line indent. ->>> def add_three_numbers_misformatted(number_one, number_two, number_three): -... result = number_one + number_two + number_three # Indented by 4 spaces. -... return result #this was only indented by 3 spaces -... -... - File "", line 3 - return result - ^ -IndentationError: unindent does not match any outer indentation level -``` - - ### Calling Functions Functions are [_called_][calls] or invoked using their name followed by `()`. -The number of arguments passed in the parentheses must match the number of parameters in the original function definition. +Dot (`.`) notation is used for calling functions defined inside a class or module. ```python >>> def number_to_the_power_of(number_one, number_two): return number_one ** number_two ... ->>> number_to_the_power_of(3,3) +>>> number_to_the_power_of(3,3) # Invoking the function with the arguments 3 and 3. 27 -``` - -A mis-match between the number of parameters and the number of arguments will raise an error: -```python +# A mis-match between the number of parameters and the number of arguments will raise an error. >>> number_to_the_power_of(4,) ... Traceback (most recent call last): File "", line 1, in TypeError: number_to_the_power_of() missing 1 required positional argument: 'number_two' -``` - - -Calling functions defined inside a class (_methods_) use `.()`, otherwise known as dot (.) notation: -```python -# This is an example of a method call of the built in str class. -# Define a variable and assign it to a string. +# Calling methods or functions in classes and modules. >>> start_text = "my silly sentence for examples." - -# Uppercase the string by calling the upper method from the str class. ->>> str.upper(start_text) +>>> str.upper(start_text) # Calling the upper() method for the built-in str class. "MY SILLY SENTENCE FOR EXAMPLES." +# Importing the math module +import math -# Below is an example of a method call of the built in list class. -# Define an empty list ->>> my_list = [] - -# Add an element to the list by calling the append method from the list class. ->>> my_list.append(start_text) ->>> print(my_list) -["my silly sentence for examples."] +>>> math.pow(2,4) # Calling the pow() function from the math module +>>> 16.0 ``` @@ -186,19 +145,6 @@ Calling functions defined inside a class (_methods_) use `.>> print(str.__doc__) -str(object='') -> str -str(bytes_or_buffer[, encoding[, errors]]) -> str - -Create a new string object from the given object. If encoding or -errors is specified, then the object must expose a data buffer -that will be decoded using the given encoding and error handler. -Otherwise, returns the result of object.__str__() (if defined) -or repr(object). -encoding defaults to sys.getdefaultencoding(). -errors defaults to 'strict'. ``` [pep257]: https://www.python.org/dev/peps/pep-0257/ -[assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements [calls]: https://docs.python.org/3/reference/expressions.html#calls [comments]: https://realpython.com/python-comments-guide/#python-commenting-basics [docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings @@ -282,13 +212,8 @@ errors defaults to 'strict'. [facts-and-myths-about-python-names]: https://nedbatchelder.com/text/names.html [function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing -[indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation [module]: https://docs.python.org/3/tutorial/modules.html -[more on functions]: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions [none]: https://docs.python.org/3/library/constants.html -[object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming [parameters]: https://docs.python.org/3/glossary.html#term-parameter [return]: https://docs.python.org/3/reference/simple_stmts.html#return -[snake case]: https://en.wikipedia.org/wiki/Snake_case [type hints]: https://docs.python.org/3/library/typing.html -[variables]: https://realpython.com/python-variables/ diff --git a/exercises/concept/guidos-gorgeous-lasagna/analysis.json b/exercises/concept/guidos-gorgeous-lasagna/analysis.json new file mode 100644 index 0000000000..5be0e2efdb --- /dev/null +++ b/exercises/concept/guidos-gorgeous-lasagna/analysis.json @@ -0,0 +1,33 @@ +{ + "summary": "There are a few changes we suggest that can bring your solution closer to ideal.", + "comments": [ + { + "comment": "python.pylint.convention", + "params": { + "lineno": "53", + "code": "C0305 trailing-newlines", + "message": [ + [ + "Trailing newlines" + ], + "" + ] + }, + "type": "actionable" + }, + { + "comment": "python.pylint.convention", + "params": { + "lineno": "1", + "code": "C0112 empty-docstring", + "message": [ + [ + "Empty module docstring" + ], + "" + ] + }, + "type": "actionable" + } + ] +} \ No newline at end of file diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py index 6b73485127..90d0102584 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py @@ -1,36 +1,14 @@ """Functions used in preparing Guido's gorgeous lasagna. -Learn about Guido, the creator of the Python language: https://en.wikipedia.org/wiki/Guido_van_Rossum -""" - -############################################################################################################## -# Hi There 👋🏽 -# For much of the Python syllabus, we will provide function stubs + docstrings. -# These are the expected function names with details to help you get started solving the exercise. -# In general, they hint at expected functionality, along with expected input and return types. -# -# However, you can completely clear out the stubs before you start coding. We recommend that -# you keep them, because they are already set up to work with the tests, which you can find in -# ./lasagna_test.py. If the tests don't find the expected names, they will throw import errors. -# -# ❗PLEASE NOTE❗ We are deviating a bit in this first exercise by asking you to practice defining -# functions & docstrings. We give you one completed stub below (bake_time_remaining), but have -# omitted the stubs and docstrings for the remaining two functions. -# -# We recommend copying the first stub + docstring, and then changing details like the function name -# and docstring text to match what is asked for in the #TODO comments and instructions. -# Once you have the correct stubs, you can get started with writing the code for each function body. -# -# ⭐ PS: You should remove explanation comment blocks like this one, and should also -# remove any comment blocks that start with #TODO (or our analyzer will nag you about them) -################################################################################################################ - +Learn about Guido, the creator of the Python language: +https://en.wikipedia.org/wiki/Guido_van_Rossum -#TODO: define the 'EXPECTED_BAKE_TIME' constant below. +This is a module docstring, used to describe the functionality +of a module and its functions and/or classes. +""" -#TODO: consider defining a 'PREPARATION_TIME' constant -# equal to the time it takes to prepare a single layer. +#TODO: define the 'EXPECTED_BAKE_TIME' constant. #TODO: Remove 'pass' and complete the 'bake_time_remaining()' function below. @@ -49,7 +27,6 @@ def bake_time_remaining(): #TODO: Define the 'preparation_time_in_minutes()' function below. -# Remember to add a docstring (you can copy and then alter the one from bake_time_remaining.) # You might also consider using 'PREPARATION_TIME' here, if you have it defined. From d15fbc6ff04b17165894553b69555e56165e601d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 25 Feb 2023 22:56:18 -0800 Subject: [PATCH 370/826] Cleaned up the instructions a bit more. --- .../.docs/instructions.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md index c3a0982e16..106e30a392 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md @@ -27,7 +27,7 @@ Implement the `bake_time_remaining()` function that takes the actual minutes the ## 3. Calculate preparation time in minutes -Implement the `preparation_time_in_minutes()` function that takes the number of layers you want to add to the lasagna as an argument and returns how many minutes you would spend making them. +Implement the `preparation_time_in_minutes(number_of_layers)` function that takes the number of layers you want to add to the lasagna as an argument and returns how many minutes you would spend making them. Assume each layer takes 2 minutes to prepare. ```python @@ -38,7 +38,7 @@ Assume each layer takes 2 minutes to prepare. ## 4. Calculate total elapsed cooking time (prep + bake) in minutes -Implement the `elapsed_time_in_minutes()` function that has two parameters: `number_of_layers` (_the number of layers added to the lasagna_) and `elapsed_bake_time` (_the number of minutes the lasagna has been baking in the oven_). +Implement the `elapsed_time_in_minutes(number_of_layers, elapsed_bake_time)` function that has two parameters: `number_of_layers` (_the number of layers added to the lasagna_) and `elapsed_bake_time` (_the number of minutes the lasagna has been baking in the oven_). This function should return the total number of minutes you've been cooking, or the sum of your preparation time and the time the lasagna has already spent baking in the oven. ```python @@ -49,14 +49,18 @@ This function should return the total number of minutes you've been cooking, or ## 5. Update the recipe with notes -Go back through the recipe, adding notes in the form of function docstrings. +Go back through the recipe, adding "notes" in the form of function docstrings. ```python def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): - """ - Return elapsed cooking time. + """Calculate the elapsed cooking time. + + :param number_of_layers: int - the number of layers in the lasagna. + :param elapsed_bake_time: int - elapsed cooking time. + :return: int - total time elapsed (in in minutes) preparing and cooking. - This function takes two numbers representing the number of layers & the time already spent - baking and calculates the total elapsed minutes spent cooking the lasagna. + This function takes two integers representing the number of lasagna layers and the + time already spent baking and calculates the total elapsed minutes spent cooking the + lasagna. """ ``` From caa95fe4b1b1d0e9c01098985108a9f77706d41d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 25 Feb 2023 23:06:43 -0800 Subject: [PATCH 371/826] Comment for counter object in assignment section. --- concepts/basics/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index 96ac4ec256..d9622fcb04 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -65,7 +65,7 @@ For example, `my_first_variable` can be re-assigned many times using `=`, and ca "Now, I'm a string." # Strings can be declared using single or double quote marks. import collections ->>> my_first_variable = collections.Counter([1,1,2,3,3,3,4,5,6,7]) +>>> my_first_variable = collections.Counter([1,1,2,3,3,3,4,5,6,7]) # Now the name has been re-bound to a Counter object. >>> print(type(my_first_variable)) From 7514d852f3191b20fb3718c3cc57187826666bba Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Mar 2023 18:59:29 -0800 Subject: [PATCH 372/826] Code review changes and additional edits to links. --- concepts/basics/.meta/config.json | 2 +- concepts/basics/about.md | 13 ++--- concepts/basics/introduction.md | 20 ++++--- concepts/basics/links.json | 57 +++++-------------- .../.docs/introduction.md | 8 +-- 5 files changed, 37 insertions(+), 63 deletions(-) diff --git a/concepts/basics/.meta/config.json b/concepts/basics/.meta/config.json index 122161cc81..86bb653b15 100644 --- a/concepts/basics/.meta/config.json +++ b/concepts/basics/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "Python is a dynamic and strongly typed object-oriented programming language in which variables can be bound and re-bound to any data type. It employs both duck typing and gradual typing (via type hints). Python uses significant indentation to denote code blocks and puts strong emphasis on code readability.", + "blurb": "Python is a dynamic and strongly typed programming language in which variables can be bound and re-bound to any data type. It employs both duck typing and gradual typing (via type hints). Python uses significant indentation to denote code blocks and puts strong emphasis on code readability.", "authors": ["BethanyG"], "contributors": ["cmccandless", "PaulT89"] } diff --git a/concepts/basics/about.md b/concepts/basics/about.md index d9622fcb04..929582357c 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -1,6 +1,6 @@ # basics -Python is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language. +Python is a [dynamic and strongly typed][dynamic typing in python] programming language. It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**. @@ -41,15 +41,15 @@ On the Python track, [variables][variables] are always written in [`snake_case`] ## Name Assignment (Variables & Constants) In Python, there are no keywords used in creating variables or constants. -Instead, programmer defined [_names_][facts-and-myths-about-python-names] (also called _variables__) can be bound to any type of object using the assignment `=` operator: ` = `. +Instead, programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables__) to any type of object using the assignment `=` operator: ` = `. A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment: ```python ->>> my_first_variable = 1 # Name bound to an integer object of value one. ->>> my_first_variable = 2 # Name re-assigned to integer value 2. +>>> my_first_variable = 1 # my_first_variable bound to an integer object of value one. +>>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2. >>> print(type(my_first_variable)) @@ -65,7 +65,7 @@ For example, `my_first_variable` can be re-assigned many times using `=`, and ca "Now, I'm a string." # Strings can be declared using single or double quote marks. import collections ->>> my_first_variable = collections.Counter([1,1,2,3,3,3,4,5,6,7]) # Now the name has been re-bound to a Counter object. +>>> my_first_variable = collections.Counter([1,1,2,3,3,3,4,5,6,7]) # Now my_first_variable has been re-bound to a Counter object. >>> print(type(my_first_variable)) @@ -106,7 +106,7 @@ Statements for the _body_ of the function begin on the line following `def` and ```python -# The body of this function is indented by 2 spaces, & prints the sum of the numbers +# The body of a function is indented by 2 spaces, & prints the sum of the numbers. def add_two_numbers(number_one, number_two): total = number_one + number_two print(total) @@ -290,7 +290,6 @@ errors defaults to 'strict'. [method objects]: https://docs.python.org/3/c-api/method.html#method-objects [module]: https://docs.python.org/3/tutorial/modules.html [none]: https://docs.python.org/3/library/constants.html -[object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming [objects]: https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy [parameters]: https://docs.python.org/3/glossary.html#term-parameter [peps]: https://www.python.org/dev/peps/ diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index 8c5447f181..e48e672651 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -1,21 +1,24 @@ # Introduction -Python is a [dynamic and strongly][dynamic typing in python] typed programming language. -It employs both [duck typing][duck typing] and [gradual typing][gradual typing] via [type hints][type hints]. +Python is a [dynamic and strongly typed][dynamic typing in python] programming language. +It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. -While Python supports many different programming _styles_, internally **everything in Python is an [object][everythings an object]**. -This includes numbers, strings, lists, and even functions. +Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**. + +Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. + +Python was created by Guido van Rossum and first released in 1991. ## Name Assignment (Variables & Constants) -Programmer defined [_names_][facts-and-myths-about-python-names] (also called _variables__) can be bound to any type of object using the assignment `=` operator: ` = `. +Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables__) to any type of object using the assignment `=` operator: ` = `. A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. ```python ->>> my_first_variable = 1 # Name bound to an integer object of value one. ->>> my_first_variable = 2 # Name re-assigned to integer value 2. +>>> my_first_variable = 1 # my_first_variable bound to an integer object of value one. +>>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2. >>> print(type(my_first_variable)) @@ -47,7 +50,7 @@ Statements for the _body_ of the function begin on the line following `def` and ```python -# The body of this function is indented by 2 spaces, & prints the sum of the numbers +# The body of a function is indented by 2 spaces, & prints the sum of the numbers. def add_two_numbers(number_one, number_two): total = number_one + number_two print(total) @@ -133,3 +136,4 @@ def complex(real=0.0, imag=0.0): [parameters]: https://docs.python.org/3/glossary.html#term-parameter [return]: https://docs.python.org/3/reference/simple_stmts.html#return [type hints]: https://docs.python.org/3/library/typing.html +[significant indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation diff --git a/concepts/basics/links.json b/concepts/basics/links.json index db223a4492..3e5561228e 100644 --- a/concepts/basics/links.json +++ b/concepts/basics/links.json @@ -1,51 +1,18 @@ [ - { - "url": "https://docs.python.org/3/", - "description": "Python documentation" - }, - { - "url": "https://www.python.org/dev/peps/pep-0008/", - "description": "PEP 8" - }, - { - "url": "https://www.python.org/dev/peps/pep-0020/", - "description": "The Zen of Python (PEP 20)" + {"url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/", + "description": "Reuven Lerner: Understanding Python Assignment" }, { - "url": "https://nedbatchelder.com/text/names.html", - "description": "Ned Batchelder: Facts and Myths about Python Names." + "url": "https://www.youtube.com/watch?v=owglNL1KQf0", + "description": "Sentdex (YouTube): Python 3 Programming Tutorial - Functions" }, { - "url": "https://realpython.com/python-variables/", - "description": "variables in Python" + "url": "https://realpython.com/documenting-python-code/#commenting-vs-documenting-code", + "description": "Real Python: Commenting vs Documenting Code." }, { - "url": "https://docs.python.org/3/tutorial/controlflow.html#defining-functions", - "description": "function definition" - }, - { - "url": "https://docs.python.org/3/tutorial/controlflow.html#default-argument-values", - "description": "default arguments" - }, - { - "url": "https://realpython.com/python-comments-guide/#python-commenting-basics", - "description": "Comments" - }, - { - "url": "https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings", - "description": "docstring" - }, - { - "url": "https://www.python.org/psf-landing/", - "description": "Python Software Foundation" - }, - { - "url": "https://www.python.org/dev/peps/", - "description": "Python Enhancement Proposals or PEPs" - }, - { - "url": "https://docs.python.org/3/reference/datamodel.html", - "description": "everything in Python is an object" + "url": "https://www.pythonmorsels.com/everything-is-an-object/", + "description": "Python Morsels: Everything is an Object" }, { "url": "https://stackoverflow.com/questions/11328920/is-python-strongly-typed", @@ -60,7 +27,11 @@ "description": "significant indentation" }, { - "url": "https://docs.python.org/3/library/doctest.html", - "description": "doctests" + "url": "https://www.digitalocean.com/community/tutorials/how-to-write-doctests-in-python", + "description": "DigitalOcean: How to Write Doctests in Python." + }, + { + "url": "https://nedbatchelder.com/blog/201803/is_python_interpreted_or_compiled_yes.html", + "description": "Ned Batchelder: Is Python Interpreted or Compiled? Yes." } ] diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index 82002c3ac2..edb9f0fe10 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -24,13 +24,13 @@ On the Python track, [variables][variables] are always written in [`snake_case`] ## Name Assignment (Variables & Constants) -Programmer defined [_names_][facts-and-myths-about-python-names] (also called _variables__) can be bound to any type of object using the assignment `=` operator: ` = `. +Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables__) to any type of object using the assignment `=` operator: ` = `. A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. ```python ->>> my_first_variable = 1 # Name bound to an integer object of value one. ->>> my_first_variable = 2 # Name re-assigned to integer value 2. +>>> my_first_variable = 1 # my_first_variable bound to an integer object of value one. +>>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2. >>> print(type(my_first_variable)) @@ -62,7 +62,7 @@ Statements for the _body_ of the function begin on the line following `def` and ```python -# The body of this function is indented by 2 spaces, & prints the sum of the numbers +# The body of a function is indented by 2 spaces, & prints the sum of the numbers. def add_two_numbers(number_one, number_two): total = number_one + number_two print(total) From bf2b5f1c1dbc9a2b75ff109185b8d327cf162d02 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Mar 2023 19:01:54 -0800 Subject: [PATCH 373/826] Delete analysis.json Snuck in by accident. --- .../guidos-gorgeous-lasagna/analysis.json | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 exercises/concept/guidos-gorgeous-lasagna/analysis.json diff --git a/exercises/concept/guidos-gorgeous-lasagna/analysis.json b/exercises/concept/guidos-gorgeous-lasagna/analysis.json deleted file mode 100644 index 5be0e2efdb..0000000000 --- a/exercises/concept/guidos-gorgeous-lasagna/analysis.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "summary": "There are a few changes we suggest that can bring your solution closer to ideal.", - "comments": [ - { - "comment": "python.pylint.convention", - "params": { - "lineno": "53", - "code": "C0305 trailing-newlines", - "message": [ - [ - "Trailing newlines" - ], - "" - ] - }, - "type": "actionable" - }, - { - "comment": "python.pylint.convention", - "params": { - "lineno": "1", - "code": "C0112 empty-docstring", - "message": [ - [ - "Empty module docstring" - ], - "" - ] - }, - "type": "actionable" - } - ] -} \ No newline at end of file From 7124a3b10e1a2770a9142de2fb950f68dc53421f Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 7 Mar 2023 10:30:58 +0100 Subject: [PATCH 374/826] Sync sieve docs with problem-specifications The sieve exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2216 ---- If you approve this pull request, I will eventually merge it. However, if you are happy with this change **please merge the pull request**, as it will get the changes into the hands of the students much more quickly. --- .../practice/sieve/.docs/instructions.md | 32 +++++++++---------- .../practice/sieve/.docs/introduction.md | 7 ++++ 2 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 exercises/practice/sieve/.docs/introduction.md diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index c3c0abeb84..ec14620ce4 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,28 +1,28 @@ # Instructions -Use the Sieve of Eratosthenes to find all the primes from 2 up to a given -number. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers. -The Sieve of Eratosthenes is a simple, ancient algorithm for finding all prime numbers up to any given limit. -It does so by iteratively marking as composite (i.e. not prime) the multiples of each prime, starting with the multiples of 2. -It does not use any division or remainder operation. +A prime number is a number that is only divisible by 1 and itself. +For example, 2, 3, 5, 7, 11, and 13 are prime numbers. -Create your range, starting at two and continuing up to and including the given limit. -(i.e. [2, limit]) +The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime. -The algorithm consists of repeating the following over and over: +A number that is **not** prime is called a "composite number". -- take the next available unmarked number in your list (it is prime) -- mark all the multiples of that number (they are not prime) +To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. +Then you repeat the following steps: -Repeat until you have processed each number in your range. +1. Find the next unmarked number in your list. This is a prime number. +2. Mark all the multiples of that prime number as composite (not prime). -When the algorithm terminates, all the numbers in the list that have not -been marked are prime. +You keep repeating these steps until you've gone through every number in your list. +At the end, all the unmarked numbers are prime. -[This wikipedia article][eratosthenes] has a useful graphic that explains the algorithm. +```exercism/note +[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. -Notice that this is a very specific algorithm, and the tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. -A good first test is to check that you do not use division or remainder operations (div, /, mod or % depending on the language). +The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. +A good first test is to check that you do not use division or remainder operations. [eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +``` diff --git a/exercises/practice/sieve/.docs/introduction.md b/exercises/practice/sieve/.docs/introduction.md new file mode 100644 index 0000000000..f6c1cf79a9 --- /dev/null +++ b/exercises/practice/sieve/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You bought a big box of random computer parts at a garage sale. +You've started putting the parts together to build custom computers. + +You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare. +You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits. From 4eb84a2d13a9c45bb2ea8bc44bf0c923352b4bf7 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 7 Mar 2023 09:37:22 +0100 Subject: [PATCH 375/826] Sync binary-search docs with problem-specifications The binary-search exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2220 ---- If you approve this pull request, I will eventually merge it. However, if you are happy with this change **please merge the pull request**, as it will get the changes into the hands of the students much more quickly. --- .../binary-search/.docs/instructions.md | 34 +++++++++++-------- .../binary-search/.docs/introduction.md | 9 +++++ 2 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 exercises/practice/binary-search/.docs/introduction.md diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index b67705406f..175c4c4ba8 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -1,24 +1,28 @@ # Instructions -Implement a binary search algorithm. +Your task is to implement a binary search algorithm. -Searching a sorted collection is a common task. -A dictionary is a sorted list of word definitions. -Given a word, one can find its definition. -A telephone book is a sorted list of people's names, addresses, and telephone numbers. -Knowing someone's name allows one to quickly find their telephone number and address. +A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. +It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -If the list to be searched contains more than a few items (a dozen, say) a binary search will require far fewer comparisons than a linear search, but it imposes the requirement that the list be sorted. +```exercism/caution +Binary search only works when a list has been sorted. +``` -In computer science, a binary search or half-interval search algorithm finds the position of a specified input value (the search "key") within an array sorted by key value. +The algorithm looks like this: -In each step, the algorithm compares the search key value with the key value of the middle element of the array. +- Divide the sorted list in half and compare the middle element with the item we're looking for. +- If the middle element is our item, then we're done. +- If the middle element is greater than our item, we can eliminate that number and all the numbers **after** it. +- If the middle element is less than our item, we can eliminate that number and all the numbers **before** it. +- Repeat the process on the part of the list that we kept. -If the keys match, then a matching element has been found and its index, or position, is returned. +Here's an example: -Otherwise, if the search key is less than the middle element's key, then the algorithm repeats its action on the sub-array to the left of the middle element or, if the search key is greater, on the sub-array to the right. +Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`. -If the remaining array to be searched is empty, then the key cannot be found in the array and a special "not found" indication is returned. - -A binary search halves the number of items to check with each iteration, so locating an item (or determining its absence) takes logarithmic time. -A binary search is a dichotomic divide and conquer search algorithm. +- We start by comparing 23 with the middle element, 16. +- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`. +- We then compare 23 with the new middle element, 28. +- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`. +- We've found our item. diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md new file mode 100644 index 0000000000..66c4b8a45c --- /dev/null +++ b/exercises/practice/binary-search/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +You have stumbled upon a group of mathematicians who are also singer-songwriters. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers. + +You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. +Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. + +You realize that you can use a binary search algorithm to quickly find a song given the title. From e7ad239b010b75d986ff17c9968e989414e0f623 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 8 Mar 2023 12:16:23 +0100 Subject: [PATCH 376/826] Sync secret-handshake docs with problem-specifications The secret-handshake exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2219 --- .../secret-handshake/.docs/instructions.md | 43 ++++++++++++++----- .../secret-handshake/.docs/introduction.md | 7 +++ 2 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 exercises/practice/secret-handshake/.docs/introduction.md diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index 2d6937ae96..77136cf0f7 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -1,24 +1,47 @@ # Instructions -> There are 10 types of people in the world: Those who understand -> binary, and those who don't. +Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. -You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake". +The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. +Start at the right-most digit and move left. -```text +The actions for each number place are: + +```plaintext 00001 = wink 00010 = double blink 00100 = close your eyes 01000 = jump - 10000 = Reverse the order of the operations in the secret handshake. ``` -Given a decimal number, convert it to the appropriate sequence of events for a secret handshake. +Let's use the number `9` as an example: + +- 9 in binary is `1001`. +- The digit that is farthest to the right is 1, so the first action is `wink`. +- Going left, the next digit is 0, so there is no double-blink. +- Going left again, the next digit is 0, so you leave your eyes open. +- Going left again, the next digit is 1, so you jump. -Here's a couple of examples: +That was the last digit, so the final code is: + +```plaintext +wink, jump +``` -Given the decimal input 3, the function would return the array ["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. +Given the number 26, which is `11010` in binary, we get the following actions: + +- double blink +- jump +- reverse actions + +The secret handshake for 26 is therefore: + +```plaintext +jump, double blink +``` -Let's now examine the input 19 which is 16+2+1 in powers of two and thus `10011` in binary. -Recalling that the addition of 16 (`10000` in binary) reverses an array and that we already know what array is returned given input 3, the array returned for input 19 is ["double blink", "wink"]. +~~~~exercism/note +If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. +[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa +~~~~ diff --git a/exercises/practice/secret-handshake/.docs/introduction.md b/exercises/practice/secret-handshake/.docs/introduction.md new file mode 100644 index 0000000000..176b92e8cf --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You are starting a secret coding club with some friends and friends-of-friends. +Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. +You don't want anyone who isn't in the know to be able to crack the code. + +You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. From 08a5a1801884340a99da41209708769e3b1a369b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Mar 2023 16:58:40 -0800 Subject: [PATCH 377/826] Regenerated Darts test file. --- exercises/practice/darts/darts_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/darts/darts_test.py b/exercises/practice/darts/darts_test.py index c720e5a787..e830199c9c 100644 --- a/exercises/practice/darts/darts_test.py +++ b/exercises/practice/darts/darts_test.py @@ -20,10 +20,10 @@ def test_on_the_middle_circle(self): def test_on_the_inner_circle(self): self.assertEqual(score(0, -1), 10) - def test_exactly_on_centre(self): + def test_exactly_on_center(self): self.assertEqual(score(0, 0), 10) - def test_near_the_centre(self): + def test_near_the_center(self): self.assertEqual(score(-0.1, -0.1), 10) def test_just_within_the_inner_circle(self): From 07e40afb06a5c8f99ba764cc6a38febb4e03cbab Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Feb 2023 17:43:27 -0800 Subject: [PATCH 378/826] Fixes type issue raised in 3190. --- exercises/concept/cater-waiter/sets_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/concept/cater-waiter/sets_test.py b/exercises/concept/cater-waiter/sets_test.py index 7ba2c2dc4f..de35ecad1d 100644 --- a/exercises/concept/cater-waiter/sets_test.py +++ b/exercises/concept/cater-waiter/sets_test.py @@ -102,6 +102,8 @@ def test_separate_appetizers(self): with self.subTest(f"variation #{variant}", inputs="dishes with appetizers", results="appetizers only"): error_message = "Expected only appetizers returned, but some dishes remain in the group." + result_type_error = f"You returned {type(separate_appetizers(item[0], item[1]))}, but a list was expected." + self.assertIsInstance(separate_appetizers(item[0], item[1]), list, msg=result_type_error) self.assertEqual(sorted(separate_appetizers(item[0], item[1])), (sorted(result)), msg=error_message) @pytest.mark.task(taskno=7) From e73b418b2fc5bb649b6db3eb17bf076988aa7c40 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 10 Mar 2023 13:28:41 -0800 Subject: [PATCH 379/826] Corrected numbers concept reference error in config.json. --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index b2222755eb..dbafb1ef98 100644 --- a/config.json +++ b/config.json @@ -605,10 +605,10 @@ "slug": "square-root", "name": "Square Root", "uuid": "c32f994a-1080-4f05-bf88-051975a75d64", - "practices": ["number"], + "practices": ["numbers"], "prerequisites": [ "basics", - "number", + "numbers", "conditionals", "loops" ], From 0e06b14e775dfce8b8c8d1385ccbe2e86c7567e4 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 14 Feb 2023 17:37:26 -0800 Subject: [PATCH 380/826] Msc link, punctuation, and formatting fixes. --- .../.approaches/alphabet/content.md | 2 +- .../.approaches/ascii-values/content.md | 8 ++--- .../.approaches/introduction.md | 34 ++++++++++++------- .../.approaches/recursion/content.md | 12 ++++--- .../.approaches/str-translate/content.md | 2 +- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/exercises/practice/rotational-cipher/.approaches/alphabet/content.md b/exercises/practice/rotational-cipher/.approaches/alphabet/content.md index e79625dbdf..a8fa1cd661 100644 --- a/exercises/practice/rotational-cipher/.approaches/alphabet/content.md +++ b/exercises/practice/rotational-cipher/.approaches/alphabet/content.md @@ -22,7 +22,7 @@ The function `rotate()` is then declared, and a variable `result` is defined as The text argument is then iterated over via a [`for loop`][for-loop]. Each element is checked to make sure it is a letter, and subsequently checked if it is uppercase or lowercase. Uppercase letters are converted to lowercase. -Then index of each letter is found in the `AlPHABET` constant. +Then the index of each letter is found in the `AlPHABET` constant. The numeric key value is added to the letter index and [modulo (`%`)][modulo] 26 is used on the result. Finally, the new number is used as an index into the `AlPHABET` constant, and the resulting letter is converted back to uppercase. diff --git a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md index c3fba5945a..b39ffed270 100644 --- a/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md +++ b/exercises/practice/rotational-cipher/.approaches/ascii-values/content.md @@ -26,24 +26,24 @@ While the uppercase letters are in the range between 65 and 91. This approach only supports the English alphabet. Non-English alphabets are not contiguous in their ascii number ranges, and are not consistently defined across platforms. -For example, the Scandinavian letter: **å** has the extended ascii value of 132, but is used in combination with Latin characters that appear in the 65-91 and 97-123 ranges. +For example, the Scandinavian letter **å** has the extended ascii value of **134**, but is used in combination with Latin-1 characters that appear in the 65-91 and 97-123 ranges. This means that a shift for an extended ascii word containing **å** won't result in an accurate alphabet position for a Scandinavian language. ~~~~ The approach starts with defining the function `rotate()`, with a variable `result` is assigned to an empty string. The elements of the text argument are then iterated over using a [`for loop`][for-loop]. -Each element is checked to see if it is a letter, and then is checked if it is an uppercase letter. +Each element is checked to see if it is a letter, and if it is a lower or uppercase letter. Python has a builtin function called `ord` that converts a [unicode][unicode] symbol to an integer representation. Unicode's first 128 code points have the same numbers as their ascii counterparts. If the element is an uppercase letter, [`ord`][ord] is used to convert the letter to an integer. The integer is added to the numeric key and then 65 is subtracted from the total. -Finally, the result is [modulo (%)][modulo] 26 (_to put the value within the 0-26 range_) and 65 is added back. +Finally, the result is [modulo (%)][modulo] 26 to put the value within the 0-26 range, and 65 is added back. This is because we want to know which letter of the alphabet the number will become. -And if the new number is over 26 we want to make sure that it "wraps around" to remain in the range of 0-26. +If the new number is over 26 we want to make sure that it "wraps around" to remain in the range of 0-26. To properly use modulo for a range we have to make sure that it starts at zero, so we subtract 65. To get back to a letter in the ascii range we add 65 and use the [`chr`][chr] method to convert the value to a letter. diff --git a/exercises/practice/rotational-cipher/.approaches/introduction.md b/exercises/practice/rotational-cipher/.approaches/introduction.md index 1c51ac5bec..4ae4591030 100644 --- a/exercises/practice/rotational-cipher/.approaches/introduction.md +++ b/exercises/practice/rotational-cipher/.approaches/introduction.md @@ -1,13 +1,14 @@ # Introduction -There are various ways to solve `rotational-cipher`. -You can for example use [ascii values][ascii], an alphabet `str` or `list`, recursion, or `str.translate`. +There are various ways to solve `Rotational Cipher`. +For example, you can use [ascii values][ascii], an alphabet `str`/`list`, recursion, or `str.translate`. ## General guidance The goal of this exercise is to shift the letters in a string by a given integer key between 0 and 26. The letter in the encrypted string is shifted for as many values (or "positions") as the value of the key. + ## Approach: Using ascii values This approach is straightforward to understand. @@ -18,11 +19,12 @@ The numbers 65-91 in the ascii range represent lowercase Latin letters, while 97 This approach only supports the English alphabet. Non-English alphabets are not contiguous in their ascii number ranges, and are not consistently defined across platforms. -For example, the Scandinavian letter: **å** has the extended ascii value of 132, but is used in combination with Latin characters that appear in the 65-91 and 97-123 ranges. +For example, the Scandinavian letter **å** has the extended ascii value of **134**, but is used in combination with Latin-1 characters that appear in the 65-91 and 97-123 ranges. This means that a shift for an extended ascii word containing **å** won't result in an accurate alphabet position for a Scandinavian language. ~~~~ + ```python def rotate(text, key): result = "" @@ -37,17 +39,18 @@ def rotate(text, key): return result ``` -For more information, check the [ascii values approach][approach-ascii-values]. +For more information, check out the [ascii values approach][approach-ascii-values]. ## Approach: Alphabet -This approach is similar to the ascii one, but it uses the index number of each letter in an alphabet string. +This approach is similar to the ascii one, but it uses the index number of each letter in a defined alphabet string. It requires making a string for all the letters in an alphabet. And unless two strings are used, you will have to convert individual letters from lower to upper case (or vice-versa). -The big advantage of this approach is the ability to use any alphabet (_although there are some issues with combining characters in Unicode._). -Here, if we want to use the scandinavian letter: **å**, we can simply insert it into our string where we want it: -`abcdefghijklmnopqrstuvwxyzå` and the rotation will work correctly. +The big advantage of this approach is the ability to use _any_ alphabet (_although there are some issues with combining characters in Unicode._). +Here, if we want to use the Scandinavian letter: **å**, we can simply insert it into our string where we want it: +`abcdefghijklmnopqrstuvwxyzå` and the key rotation will work correctly. + ```python # This only uses English characters @@ -66,7 +69,7 @@ def rotate(text, key): return result ``` -For more information, check the [Alphabet approach][approach-alphabet]. +For more information, see the [Alphabet approach][approach-alphabet]. ## Approach: Str translate @@ -77,6 +80,7 @@ The benefit of this approach is that it has no visible loop, making the code mor `str.translate` **still loops over the `string`** even if it is not visibly doing so. ~~~~ + ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz" @@ -85,7 +89,8 @@ def rotate(text, key): return text.translate(str.maketrans(AlPHABET + AlPHABET.upper(), translator + translator.upper())) ``` -For more information, check the [Str translate approach][approach-str-translate]. +For more information, check out the [Str translate approach][approach-str-translate]. + ## Approach: Recursion @@ -96,8 +101,11 @@ This approach can be more concise than other approaches, and may also be more re ~~~~exercism/caution Python does not have any tail-call optimization and has a default [recursion limit][recursion-limit] of 1000 calls on the stack. Calculate your base case carefully to avoid errors. + +[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit ~~~~ + ```python AlPHABET = "abcdefghijklmnopqrstuvwxyz" @@ -114,11 +122,12 @@ def rotate(text, key): return first_letter + rotate(rest, key) ``` -For more information, check the [Recursion approach][approach-recursion]. +For more information, read the [Recursion approach][approach-recursion]. ## Benchmark -For more information, check the [Performance article][article-performance]. +For more information on the speed of these various approaches, check out the Rotational Cipher [Performance article][article-performance]. + [ascii]: https://en.wikipedia.org/wiki/ASCII [approach-recursion]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/recursion @@ -126,5 +135,4 @@ For more information, check the [Performance article][article-performance]. [approach-ascii-values]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/ascii-values [approach-alphabet]: https://exercism.org/tracks/python/exercises/rotational-cipher/approaches/alphabet [article-performance]: https://exercism.org/tracks/python/exercises/rotational-cipher/articles/performance -[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit [str-translate]: https://docs.python.org/3/library/stdtypes.html?highlight=str%20translate#str.translate diff --git a/exercises/practice/rotational-cipher/.approaches/recursion/content.md b/exercises/practice/rotational-cipher/.approaches/recursion/content.md index 689f8aaed7..8111f790e0 100644 --- a/exercises/practice/rotational-cipher/.approaches/recursion/content.md +++ b/exercises/practice/rotational-cipher/.approaches/recursion/content.md @@ -18,7 +18,7 @@ def rotate(text, key): This approach uses the same logic as that of the `alphabet` approach, but instead of a `loop`, [concept:python/recursion]() is used to parse the text and solve the problem. -Recursion is a programming technique where a function calls itself. +[Recursion][recursion] is a programming technique where a function calls itself. It is powerful, but can be more tricky to implement than a `while loop` or `for loop`. Recursive techniques are more common in functional programming languages like [Elixir][elixir], [Haskell][haskell], and [Clojure][clojure] than they are in Python. @@ -28,16 +28,18 @@ Then the `rotate` function can execute the same code again with new values. We can build a long chain or "stack" of ` + rotate(rest)` calls until the `rest` variable is exhausted and the code adds `""`. That translates to something like this: ` + + + + ""`. + ~~~~exercism/note By default, we can't have a function call itself more than 1000 times. -Code that exceeds this recursion limit will throw a RecursionError. -While it is possible to adjust the recursion limit, doing so risks crashing Python and may also crash your system. +Code that exceeds this recursion limit will throw a [`RecursionError`][recursion-error]. +While it is possible to [adjust the recursion limit][recursion-limit], doing so risks crashing Python and may also crash your system. Casually raising the recursion limit is not recommended. + +[recursion-error]: https://docs.python.org/3/library/exceptions.html#RecursionError +[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit ~~~~ [clojure]: https://exercism.org/tracks/clojure [elixir]: https://exercism.org/tracks/elixir [haskell]: https://exercism.org/tracks/haskell [recursion]: https://realpython.com/python-thinking-recursively/ -[recursion-error]: https://docs.python.org/3/library/exceptions.html#RecursionError -[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit diff --git a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md index b80da8d2ec..b3d37110c8 100644 --- a/exercises/practice/rotational-cipher/.approaches/str-translate/content.md +++ b/exercises/practice/rotational-cipher/.approaches/str-translate/content.md @@ -10,7 +10,7 @@ def rotate(text, key): This approach uses the [`.translate`][translate] method. `translate` takes a translation table as an argument. -To create a translation table we use [str.`makestrans`][maketrans]. +To create a translation table we use [`str.makestrans`][maketrans]. This approach starts with defining a constant of all the lowercase letters in the alphabet. Then the function `rotate()` is declared. From bbf5e0c1b12e5096fae0a1f55e08dfacdb13f416 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 14 Feb 2023 17:46:52 -0800 Subject: [PATCH 381/826] Additional link fixes. --- .../practice/rotational-cipher/.approaches/introduction.md | 3 +-- .../rotational-cipher/.approaches/recursion/content.md | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/exercises/practice/rotational-cipher/.approaches/introduction.md b/exercises/practice/rotational-cipher/.approaches/introduction.md index 4ae4591030..047d9950ec 100644 --- a/exercises/practice/rotational-cipher/.approaches/introduction.md +++ b/exercises/practice/rotational-cipher/.approaches/introduction.md @@ -99,10 +99,9 @@ A recursive function is a function that calls itself. This approach can be more concise than other approaches, and may also be more readable for some audiences. ~~~~exercism/caution -Python does not have any tail-call optimization and has a default [recursion limit][recursion-limit] of 1000 calls on the stack. +Python does not have any tail-call optimization and has a default [recursion limit](https://docs.python.org/3/library/sys.html#sys.setrecursionlimit) of 1000 calls on the stack. Calculate your base case carefully to avoid errors. -[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit ~~~~ diff --git a/exercises/practice/rotational-cipher/.approaches/recursion/content.md b/exercises/practice/rotational-cipher/.approaches/recursion/content.md index 8111f790e0..48ff3facae 100644 --- a/exercises/practice/rotational-cipher/.approaches/recursion/content.md +++ b/exercises/practice/rotational-cipher/.approaches/recursion/content.md @@ -31,12 +31,10 @@ That translates to something like this: ` + + + Date: Tue, 14 Mar 2023 16:26:34 +0100 Subject: [PATCH 382/826] Sync sum-of-multiples docs with problem-specifications The sum-of-multiples exercise has been improved upstream in the problem-specifications repository. For more context, please see the pull request that updated the exercise: - https://github.com/exercism/problem-specifications/pull/2231 --- .../sum-of-multiples/.docs/instructions.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index ff7fdffd86..7b7ec006e2 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -1,7 +1,18 @@ # Instructions -Given a number, find the sum of all the unique multiples of particular numbers up to but not including that number. +Given a list of factors and a limit, add up all the unique multiples of the factors that are less than the limit. +All inputs will be greater than or equal to zero. -If we list all the natural numbers below 20 that are multiples of 3 or 5, we get 3, 5, 6, 9, 10, 12, 15, and 18. +## Example -The sum of these multiples is 78. +Suppose the limit is 20 and the list of factors is [3, 5]. +We need to find the sum of all unique multiples of 3 and 5 that are less than 20. + +Multiples of 3 less than 20: 3, 6, 9, 12, 15, 18 +Multiples of 5 less than 20: 5, 10, 15 + +The unique multiples are: 3, 5, 6, 9, 10, 12, 15, 18 + +The sum of the unique multiples is: 3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78 + +So, the answer is 78. From c4d8546f37c741d46d39a978a663b615f371c3d8 Mon Sep 17 00:00:00 2001 From: Hoai-Thu Vuong Date: Fri, 10 Mar 2023 14:18:29 +0700 Subject: [PATCH 383/826] fix typo in the instruction of guidos-gorgeous - remove double in --- exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md index 106e30a392..2d61cec837 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md @@ -57,7 +57,7 @@ def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): :param number_of_layers: int - the number of layers in the lasagna. :param elapsed_bake_time: int - elapsed cooking time. - :return: int - total time elapsed (in in minutes) preparing and cooking. + :return: int - total time elapsed (in minutes) preparing and cooking. This function takes two integers representing the number of lasagna layers and the time already spent baking and calculates the total elapsed minutes spent cooking the From 8e5c58583b195ebc57cb0b5a866ad3ced9e5d872 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Thu, 16 Mar 2023 12:38:59 +0100 Subject: [PATCH 384/826] Sync binary-search docs with problem-specifications There were a few follow-on tweaks to binary-search. For context, this is part of the project to overhaul all the practice exercises. You can read about that here: https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 ---- If you approve this pull request, I will eventually merge it. However, if you are happy with this change **please merge the pull request**, as it will get the changes into the hands of the students much more quickly. If this pull request contradicts the exercise on your track, **please add a review with _request changes_**. This will block the pull request from getting merged. Otherwise we aim to take an optimistic merging approach. If you wish to suggest tweaks to these changes, please open a pull request to the exercism/problem-specifications repository to discuss, so that everyone who has an interest in the shared exercise descriptions can participate. --- .../practice/binary-search/.docs/instructions.md | 15 ++++++++------- .../practice/binary-search/.docs/introduction.md | 6 +++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index 175c4c4ba8..d7f1c89922 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -5,17 +5,18 @@ Your task is to implement a binary search algorithm. A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -```exercism/caution +~~~~exercism/caution Binary search only works when a list has been sorted. -``` +~~~~ The algorithm looks like this: -- Divide the sorted list in half and compare the middle element with the item we're looking for. -- If the middle element is our item, then we're done. -- If the middle element is greater than our item, we can eliminate that number and all the numbers **after** it. -- If the middle element is less than our item, we can eliminate that number and all the numbers **before** it. -- Repeat the process on the part of the list that we kept. +- Find the middle element of a sorted list and compare it with the item we're looking for. +- If the middle element is our item, then we're done! +- If the middle element is greater than our item, we can eliminate that element and all the elements **after** it. +- If the middle element is less than our item, we can eliminate that element and all the elements **before** it. +- If every element of the list has been eliminated then the item is not in the list. +- Otherwise, repeat the process on the part of the list that has not been eliminated. Here's an example: diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md index 66c4b8a45c..03496599e7 100644 --- a/exercises/practice/binary-search/.docs/introduction.md +++ b/exercises/practice/binary-search/.docs/introduction.md @@ -1,9 +1,13 @@ # Introduction You have stumbled upon a group of mathematicians who are also singer-songwriters. -They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers (like [0][zero] or [73][seventy-three] or [6174][kaprekars-constant]). You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. You realize that you can use a binary search algorithm to quickly find a song given the title. + +[zero]: https://en.wikipedia.org/wiki/0 +[seventy-three]: https://en.wikipedia.org/wiki/73_(number) +[kaprekars-constant]: https://en.wikipedia.org/wiki/6174_(number) From d783bd143271a8e1db36d655adfd8039754a9268 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sat, 18 Mar 2023 11:22:17 +0100 Subject: [PATCH 385/826] Update INSTALLATION.md --- docs/INSTALLATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 9481ce512e..ad3463291c 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -6,7 +6,7 @@ Real Python also offers a [nice guide][helpful guide] to installation on various Finally, these posts by Brett Cannon [A quick-and-dirty guide][quick-and-dirty] and [Why you should use `python -m pip`][python-m-pip], give very helpful advice on how to manage Python installations and packages. **Note for MacOS users:** prior to MacOS Monterey (12.3), `Python 2.7` came pre-installed with the operating system. -Using `Python 2.7` with exercsim or most other programs is not recommended. +Using `Python 2.7` with Exercism or most other programs is not recommended. You should instead install Python 3 via one of the methods detailed below. As of MacOS Monterey (12.3), no version of Python will be pre-installed via MacOS. From 5b1ef82c4e2ffd6ad16e3d5b79f2fba047c88f2b Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sat, 18 Mar 2023 11:54:00 +0100 Subject: [PATCH 386/826] basics: fix markdown --- concepts/basics/about.md | 2 +- concepts/basics/introduction.md | 2 +- exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index 929582357c..30ea083022 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -41,7 +41,7 @@ On the Python track, [variables][variables] are always written in [`snake_case`] ## Name Assignment (Variables & Constants) In Python, there are no keywords used in creating variables or constants. -Instead, programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables__) to any type of object using the assignment `=` operator: ` = `. +Instead, programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: ` = `. A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment: diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index e48e672651..34e2a8804d 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -12,7 +12,7 @@ Python was created by Guido van Rossum and first released in 1991. ## Name Assignment (Variables & Constants) -Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables__) to any type of object using the assignment `=` operator: ` = `. +Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: ` = `. A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index edb9f0fe10..f9be66c3fe 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -24,7 +24,7 @@ On the Python track, [variables][variables] are always written in [`snake_case`] ## Name Assignment (Variables & Constants) -Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables__) to any type of object using the assignment `=` operator: ` = `. +Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: ` = `. A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. From 9f6787133a863290b59d71a2a0032943ca5303cf Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 22 Mar 2023 20:29:25 +0100 Subject: [PATCH 387/826] string-methods: fix typo --- concepts/string-methods/about.md | 4 ++-- exercises/concept/little-sisters-essay/.docs/introduction.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/concepts/string-methods/about.md b/concepts/string-methods/about.md index 81eaffb0a7..e01cbaf890 100644 --- a/concepts/string-methods/about.md +++ b/concepts/string-methods/about.md @@ -44,7 +44,7 @@ There may also be [locale][locale] rules in place for a language or character se >>> man_in_hat_th = 'ู้ชายในหมวก' >>> man_in_hat_ru = 'mужчина в шляпе' >>> man_in_hat_ko = '모자를 쓴 남자' ->>> main_in_hat_en = 'the man in the hat.' +>>> man_in_hat_en = 'the man in the hat.' >>> man_in_hat_th.title() 'ผู้ชายในหมวก' @@ -55,7 +55,7 @@ There may also be [locale][locale] rules in place for a language or character se >>> man_in_hat_ko.title() '모자를 쓴 남자' ->> main_in_hat_en.title() +>> man_in_hat_en.title() 'The Man In The Hat.' ``` diff --git a/exercises/concept/little-sisters-essay/.docs/introduction.md b/exercises/concept/little-sisters-essay/.docs/introduction.md index 1b0e953112..24358f2fc2 100644 --- a/exercises/concept/little-sisters-essay/.docs/introduction.md +++ b/exercises/concept/little-sisters-essay/.docs/introduction.md @@ -24,7 +24,7 @@ There may also be [locale][locale] rules in place for a language or character se man_in_hat_th = 'ู้ชายในหมวก' man_in_hat_ru = 'mужчина в шляпе' man_in_hat_ko = '모자를 쓴 남자' -main_in_hat_en = 'the man in the hat.' +man_in_hat_en = 'the man in the hat.' >>> man_in_hat_th.title() 'ผู้ชายในหมวก' @@ -35,7 +35,7 @@ main_in_hat_en = 'the man in the hat.' >>> man_in_hat_ko.title() '모자를 쓴 남자' ->> main_in_hat_en.title() +>> man_in_hat_en.title() 'The Man In The Hat.' ``` From d8e8cb6ca70423c3b9db43b0ff64e16e4050bf2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Mar 2023 23:56:56 +0000 Subject: [PATCH 388/826] Bump actions/stale from 7 to 8 Bumps [actions/stale](https://github.com/actions/stale) from 7 to 8. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4742a0db0a..41156435d8 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v7 + - uses: actions/stale@v8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 From 84c35bac97da78c09f9044f85f6a6335589124b6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 30 Mar 2023 20:50:14 -0700 Subject: [PATCH 389/826] Removed numbers concept exercise from Palindrome Products practices array. --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index dbafb1ef98..1d118f79c8 100644 --- a/config.json +++ b/config.json @@ -1441,7 +1441,7 @@ "slug": "palindrome-products", "name": "Palindrome Products", "uuid": "fa795dcc-d390-4e98-880c-6e8e638485e3", - "practices": ["functions", "function-arguments", "numbers"], + "practices": ["functions", "function-arguments"], "prerequisites": [ "basics", "bools", From 9bbd540c5c752226296e586162cd76ffe4b6dde8 Mon Sep 17 00:00:00 2001 From: Hanson Char Date: Thu, 6 Apr 2023 07:47:02 -0700 Subject: [PATCH 390/826] Minor simplification to is_pangram in the Bit field approach --- exercises/practice/pangram/.approaches/bitfield/content.md | 4 ++-- .../practice/pangram/.articles/performance/code/Benchmark.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/pangram/.approaches/bitfield/content.md b/exercises/practice/pangram/.approaches/bitfield/content.md index 20e460fea0..b292540567 100644 --- a/exercises/practice/pangram/.approaches/bitfield/content.md +++ b/exercises/practice/pangram/.approaches/bitfield/content.md @@ -9,9 +9,9 @@ ALL_26_BITS_SET = 67108863 def is_pangram(sentence): letter_flags = 0 for letter in sentence: - if letter >= 'a' and letter <= 'z': + if 'a' <= letter <= 'z': letter_flags |= 1 << ord(letter) - A_LCASE - elif letter >= 'A' and letter <= 'Z': + elif 'A' <= letter <= 'Z': letter_flags |= 1 << ord(letter) - A_UCASE return letter_flags == ALL_26_BITS_SET diff --git a/exercises/practice/pangram/.articles/performance/code/Benchmark.py b/exercises/practice/pangram/.articles/performance/code/Benchmark.py index 729cc967dc..1b42374447 100644 --- a/exercises/practice/pangram/.articles/performance/code/Benchmark.py +++ b/exercises/practice/pangram/.articles/performance/code/Benchmark.py @@ -43,9 +43,9 @@ def is_pangram(sentence): def is_pangram(sentence): letter_flags = 0 for letter in sentence: - if letter >= 'a' and letter <= 'z': + if 'a' <= letter <= 'z': letter_flags |= 1 << (ord(letter) - A_LCASE) - elif letter >= 'A' and letter <= 'Z': + elif 'A' <= letter <= 'Z': letter_flags |= 1 << (ord(letter) - A_UCASE) return letter_flags == ALL_26_BITS_SET From c865759d7d45449eabbcfa73b9f08dc7adb4975f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 7 Apr 2023 04:19:47 -0700 Subject: [PATCH 391/826] Updated refrences to Python 3.10 --> 3.11. (#3384) --- CONTRIBUTING.md | 13 +++++++------ README.md | 23 ++++++++++++++--------- docs/ABOUT.md | 12 ++++++++---- docs/GENERATOR.md | 17 +++++++++++++---- docs/INSTALLATION.md | 18 ++++++++++++------ docs/LEARNING.md | 5 +++-- docs/TESTS.md | 2 +- exercises/shared/.docs/help.md | 1 + 8 files changed, 59 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 691ca9f92f..7f082a5632 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,14 +28,13 @@ Please read this [community blog post](https://exercism.org/blog/freeing-our-mai **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. -🌟   Track exercises support Python `3.7` - `3.10.6`. +🌟   Track exercises support Python `3.7` - `3.11.2`. Exceptions to this support are noted where they occur. - 🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.10.6`. +🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`. -Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴  . -Concept exercises are constrained to a small set of language or syntax features. -Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. -These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory. +Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴 . +Concept exercises are constrained to a small set of language or syntax features. +Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory.
@@ -430,3 +429,5 @@ configlet generate --spec-path path/to/problem/specifications [version-tagged-language-features]: https://docs.python.org/3/library/stdtypes.html#dict.popitem [website-contributing-section]: https://exercism.org/docs/building [yapf]: https://github.com/google/yapf + +[config-json]: https://github.com/exercism/python/blob/main/config.json diff --git a/README.md b/README.md index 6493081636..1c82f33b9d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

Exercism Python Track

                          [![Discourse topics](https://img.shields.io/discourse/topics?color=8A08E6&label=Connect%20&labelColor=FFDF58&logo=Discourse&logoColor=8A08E6&server=https%3A%2F%2Fforum.exercism.org&style=social)](https://forum.exercism.org) -  [![Exercism_II](https://img.shields.io/badge/Exercism--Built-9101FF?logo=python&logoColor=FFDF58&labelColor=3D7AAB&label=Python%203.10%20Powered)](https://exercism.org) +  [![Exercism_II](https://img.shields.io/badge/Exercism--Built-9101FF?logo=python&logoColor=FFDF58&labelColor=3D7AAB&label=Python%203.11%20Powered)](https://exercism.org)   [![Exercism_III](https://img.shields.io/badge/PAUSED-C73D4E?labelColor=3D454D&label=Contributions)](https://exercism.org/blog/freeing-our-maintainers)   [![Build Status](https://github.com/exercism/python/workflows/Exercises%20check/badge.svg)](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22) @@ -17,11 +17,13 @@ Hi.  👋🏽  👋  **We are happy you are here.**  🎉&nb **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. -🌟   Track exercises support Python `3.7` - `3.10.6`. +🌟   Track exercises support Python `3.7` - `3.11.2`. Exceptions to this support are noted where they occur. - 🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.10.6`. +🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`. -Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴  . Concept exercises are constrained to a small set of language or syntax features. Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory. +Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴 . +Concept exercises are constrained to a small set of language or syntax features. +Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory.

@@ -43,24 +45,25 @@ It might also be helpful to look at [Being a Good Community Member][being-a-good We 💛 💙   our community. **`But our maintainers are not accepting community contributions at this time.`** -Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details. +Please read this [community blog post][freeing-maintainers] for details.
- + Here to suggest a new feature or new exercise?? **Hooray!**  🎉   -We'd love if you did that via our [Exercism Community Forum](https://forum.exercism.org/). Please keep in mind [Chesterton's Fence][chestertons-fence]. +We'd love if you did that via our [Exercism Community Forum](https://forum.exercism.org/). +Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence]. _Thoughtful suggestions will likely result faster & more enthusiastic responses from volunteers._
- ✨ 🦄  _**Want to jump directly into Exercism specifications & detail?**_      [Structure][exercism-track-structure] **|** [Tasks][exercism-tasks] **|** [Concepts][exercism-concepts] **|** [Concept Exercises][concept-exercises] **|** [Practice Exercises][practice-exercises] **|** [Presentation][exercise-presentation]      [Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_✨ version in [contributing][website-contributing-section] on exercism.org_) -

+
+
## Python Software and Documentation @@ -94,10 +97,12 @@ This repository uses the [MIT License](/LICENSE). [exercism-track-structure]: https://github.com/exercism/docs/tree/main/building/tracks [exercism-website]: https://exercism.org/ [exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md +[freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers [practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md [prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md [psf-license]: https://docs.python.org/3/license.html#psf-license [python-syllabus]: https://exercism.org/tracks/python/concepts +[suggesting-improvements]: https://github.com/exercism/docs/blob/main/community/good-member/suggesting-exercise-improvements.md [the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md [website-contributing-section]: https://exercism.org/docs/building [zero-clause-bsd]: https://docs.python.org/3/license.html#zero-clause-bsd-license-for-code-in-the-python-release-documentation diff --git a/docs/ABOUT.md b/docs/ABOUT.md index 9b53653fd1..05c56799ac 100644 --- a/docs/ABOUT.md +++ b/docs/ABOUT.md @@ -20,13 +20,14 @@ Code can be written and executed from the command line, in an interactive interp The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies and perspectives on the language. -This track currently uses `Python 3.9.0`. +Tests and tooling for this track currently support `3.7` - `3.10.6` (_tests_) and [`Python 3.11`][311-new-features] (_tooling_). It is highly recommended that students upgrade to at least `Python 3.8`, as some features used by this track may not be supported in earlier versions. -That being said, most exercises can be completed using Python 3.7, and many can be worked in Python 3.6. -We will note when a feature is only available in a certain version. +That being said, most of the exercises will work with `Python 3.6+`, and many are compatible with `Python 2.7+`. +But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases]. +We will try to note when a feature is only available in a certain version. -Complete documentation for the current release of Python (3.9.7) can be found at [docs.python.org][python docs]. +Complete documentation for the current release of Python (3.11.2) can be found at [docs.python.org][python docs]. - [Python Tutorial][python tutorial] - [Python Library Reference][python library reference] @@ -36,6 +37,9 @@ Complete documentation for the current release of Python (3.9.7) can be found at - [Python Glossary of Terms][python glossary of terms] + +[311-new-features]: https://docs.python.org/3/whatsnew/3.11.html +[active-python-releases]: https://www.python.org/downloads/ [duck typing]: https://en.wikipedia.org/wiki/Duck_typing [dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed [editors for python]: https://djangostars.com/blog/python-ide/ diff --git a/docs/GENERATOR.md b/docs/GENERATOR.md index d1a8a1da49..271c03d71f 100644 --- a/docs/GENERATOR.md +++ b/docs/GENERATOR.md @@ -1,7 +1,8 @@ # Exercism Python Track Test Generator -The Python track uses a generator script and [Jinja2] templates for -creating test files from the canonical data. +The Python track uses a generator script and [Jinja2][Jinja2] templates for +creating test files from Exercism's [canonical data][canonical data]. + ## Table of Contents @@ -15,14 +16,16 @@ creating test files from the canonical data. * [Learning Jinja](#learning-jinja) * [Creating a templates](#creating-a-templates) + ## Script Usage -Test generation requires a local copy of the [problem-specifications] repository. +Test generation requires a local copy of the [problem-specifications][problem-specifications] repository. The script should be run from the root `python` directory, in order to correctly access the exercises. Run `bin/generate_tests.py --help` for usage information. + ## Test Templates Test templates support [Jinja2] syntax, and have the following context @@ -42,6 +45,7 @@ Additionally, the following template filters are added for convenience: - `error_case`: Checks if a test case expects an error to be thrown (ex: `{% for case in cases%}{% if case is error_case}`) - `regex_replace`: Regex string replacement (ex: `{{ "abc123" | regex_replace("\\d", "D") }}` -> `abcDDD`) + ### Conventions - General-use macros for highly repeated template structures are defined in `config/generator_macros.j2`. @@ -63,6 +67,7 @@ files. # Additional tests for this track ``` + ### Layout Most templates will look something like this: @@ -83,6 +88,7 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ macros.footer() }} ``` + ### Overriding Imports The names imported in `macros.header()` may be overridden by adding @@ -92,6 +98,7 @@ a list of alternate names to import (ex:`clock`): {{ macros.header(["Clock"])}} ``` + ### Ignoring Properties On rare occasion, it may be necessary to filter out properties that @@ -102,6 +109,7 @@ are not tested in this track. The `header` macro also accepts an {{ macros.header(ignore=["scores"]) }} ``` + ## Learning Jinja Starting with the [Jinja Documentation] is highly recommended, but a complete reading is not strictly necessary. @@ -127,8 +135,9 @@ Additional Resources: +[Jinja Documentation]: https://jinja.palletsprojects.com/en/3.1.x/ [Jinja2]: https://palletsprojects.com/p/jinja/ -[Jinja Documentation]: https://jinja.palletsprojects.com/en/2.10.x/ [Primer on Jinja Templating]: https://realpython.com/primer-on-jinja-templating/ [Python Jinja tutorial]: http://zetcode.com/python/jinja/ +[canonical data]: https://github.com/exercism/problem-specifications/tree/main/exercises [problem-specifications]: https://github.com/exercism/problem-specifications diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index ad3463291c..8bf42a0452 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -7,7 +7,7 @@ Finally, these posts by Brett Cannon [A quick-and-dirty guide][quick-and-dirty] **Note for MacOS users:** prior to MacOS Monterey (12.3), `Python 2.7` came pre-installed with the operating system. Using `Python 2.7` with Exercism or most other programs is not recommended. -You should instead install Python 3 via one of the methods detailed below. +You should instead install [Python 3][Python-three downloads] via one of the methods detailed below. As of MacOS Monterey (12.3), no version of Python will be pre-installed via MacOS. Some quick links into the documentation by operating system: @@ -18,12 +18,18 @@ Some quick links into the documentation by operating system: We recommend reviewing some of the methods outlined in the Real Python article [Installing Python][installing-python] or the articles by Brett Cannon linked above. -Exercism tests and tooling currently support `Python 3.8` (_tests_) and `Python 3.9` (_tooling_). -This means that the [newest features of Python `3.10`][310-new-features] are **not** currently supported. -Please refer to the [Python 3.9.x documentation][3.9 docs] for what is currently supported. +Exercism tests and tooling currently support `3.7` - `3.10.6` (_tests_) and [`Python 3.11`][311-new-features] (_tooling_). +Exceptions to this support are noted where they occur. +Most of the exercises will work with `Python 3.6+`, and many are compatible with `Python 2.7+`. +But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases]. -[3.9 docs]: https://docs.python.org/3.9/ -[310-new-features]: https://docs.python.org/3/whatsnew/3.10.html + +Please refer to the [Python 3.11.x documentation][3.11 docs] for what is currently supported. + +[3.11 docs]: https://docs.python.org/3.11/ +[311-new-features]: https://docs.python.org/3/whatsnew/3.11.html +[Python-three downloads]: https://www.python.org/downloads/ +[active-python-releases]: https://www.python.org/downloads/ [helpful guide]: https://realpython.com/installing-python/ [installing-python]: https://realpython.com/installing-python/#what-your-options-are_1 [macos]: https://docs.python.org/3/using/mac.html diff --git a/docs/LEARNING.md b/docs/LEARNING.md index 15e4fd8021..f9cb1b2205 100644 --- a/docs/LEARNING.md +++ b/docs/LEARNING.md @@ -21,12 +21,13 @@ Below you will find some additional jumping-off places to start your learning jo - [Think Python][Think Python] - [Python for Non-Programmers][python-for-non-programmers] - [Python 4 Everyone][python4everyone] -- [Introduction to Computer Science and Programming in Python (MIT)][mitocw600] - [Googles Python Class][googles python class] - [Microsoft's Python Learning Path][MS Python] -- [PyCharm EDU **IDE** and **Courses**][pycharm edu] (_paid_) +- [Introduction to Computer Science and Programming in Python (MIT)][mitocw600] +- [Harvard CS50P][CS50P] +[CS50P]: https://pll.harvard.edu/course/cs50s-introduction-programming-python?delta=0 [Learn X in Y minutes]: https://learnxinyminutes.com/docs/python3/ [MS Python]: https://docs.microsoft.com/en-us/learn/paths/python-language/ [Python Documentation Tutorial]: https://docs.python.org/3/tutorial/index.html diff --git a/docs/TESTS.md b/docs/TESTS.md index 9e522b12d5..00ff924cd7 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -272,5 +272,5 @@ export PATH=”$PATH:{python_directory}}” [python command line]: https://docs.python.org/3/using/cmdline.html [python-m-pip]: https://snarky.ca/why-you-should-use-python-m-pip/ [quick-and-dirty]: https://snarky.ca/a-quick-and-dirty-guide-on-how-to-install-packages-for-python/ -[tutorial from pycqa.org]: https://pylint.pycqa.org/en/latest/tutorial.html +[tutorial from pycqa.org]: https://pylint.pycqa.org/en/v2.17.2/tutorial.html [working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers diff --git a/exercises/shared/.docs/help.md b/exercises/shared/.docs/help.md index 0e105b02d9..ef95bd6193 100644 --- a/exercises/shared/.docs/help.md +++ b/exercises/shared/.docs/help.md @@ -3,6 +3,7 @@ Below are some resources for getting help if you run into trouble: - [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) - [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. - [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. - [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. From 6cc4b919ec04e6e2e777e58ff926e77054b4c26c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 7 Apr 2023 04:20:05 -0700 Subject: [PATCH 392/826] [Track CI] Upgrade Workflow to use Python `3.11.2` & Pytest `7.2.2` (#3382) * Upgraded CI workflow to use Python 3.11.2 and Pytest 7.2.2 * Pinned Pylint version to ~=2.17.1 * Added tomli to test_exercises.py imports. * Adding in tomli for dependancies. * Need to use latest build of Black due to Python 3.11 and packaging problems with tomli * Regenerated test files for exercises failing CI. * Try removing tomli to see if it fixes CI for Py3.11 * And more version fishing for black, which is the culprit here. * Regeneraed test files with black 22.3.0, since black 23.3.0 is incompatible with Python 3.11 due to tomli conflicts. * Trying a different strategy. Using only 3.11 for the main container. * Must import tomllib, part of exteded standard lib not core. * Wrapped tomllib in a try-catch and aliased importing tomli. Updated requirements to only install tmli on 3.10 and below. * Forgot to update the imports in the generator. * Added paasio test changes here, since this changes the CI checker to use Python 3.11. * Trying a msg= approach. --- .github/workflows/ci-workflow.yml | 6 +++--- bin/data.py | 11 +++++++++-- bin/generate_tests.py | 8 +++++++- exercises/practice/paasio/paasio_test.py | 20 ++++++++------------ requirements-generator.txt | 4 ++-- requirements.txt | 5 +++-- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index fe8ff089a6..01ddab0cac 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.10.6 + python-version: 3.11.2 - name: Download & Install dependencies run: | @@ -53,7 +53,7 @@ jobs: needs: housekeeping strategy: matrix: - python-version: [3.7, 3.8, 3.9, 3.10.6] + python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - uses: actions/checkout@v3 @@ -66,7 +66,7 @@ jobs: run: pip install dataclasses - name: Install pytest - run: pip install pytest~=7.1.2 + run: pip install pytest~=7.2.2 - name: Check exercises run: | diff --git a/bin/data.py b/bin/data.py index 54dec7612b..7fce51b908 100644 --- a/bin/data.py +++ b/bin/data.py @@ -4,9 +4,16 @@ from itertools import chain import json from pathlib import Path -import tomli from typing import List, Any, Dict, Type +# Tomli was subsumed into Python 3.11.x, but was renamed to to tomllib. +# This avoids ci failures for Python < 3.11.2. +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib + + def _custom_dataclass_init(self, *args, **kwargs): # print(self.__class__.__name__, "__init__") @@ -355,7 +362,7 @@ class TestsTOML: @classmethod def load(cls, toml_path: Path): with toml_path.open("rb") as f: - data = tomli.load(f) + data = tomllib.load(f) return cls({uuid: TestCaseTOML(uuid, *opts) for uuid, opts in data.items() if diff --git a/bin/generate_tests.py b/bin/generate_tests.py index d0406aad5e..8dc74936b1 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -34,11 +34,17 @@ from itertools import repeat from string import punctuation, whitespace from subprocess import check_call -import tomli from tempfile import NamedTemporaryFile from textwrap import wrap from typing import Any, Dict, List, NoReturn, Union +# Tomli was subsumed into Python 3.11.x, but was renamed to to tomllib. +# This avoids ci failures for Python < 3.11.2. +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib + from jinja2 import Environment, FileSystemLoader, TemplateNotFound, UndefinedError from dateutil.parser import parse diff --git a/exercises/practice/paasio/paasio_test.py b/exercises/practice/paasio/paasio_test.py index 07a5cea7a5..c5c7ff7317 100644 --- a/exercises/practice/paasio/paasio_test.py +++ b/exercises/practice/paasio/paasio_test.py @@ -199,13 +199,13 @@ def test_meteredsocket_stats_read_only(self): self.assertEqual(282, socket.send_bytes) self.assertEqual(258, socket.recv_ops) self.assertEqual(259, socket.recv_bytes) - with self.assertRaisesRegex(AttributeError, "can't set"): + with self.assertRaises(AttributeError, msg="property 'send_ops' of 'MeteredSocket' object has no setter"): socket.send_ops = 0 - with self.assertRaisesRegex(AttributeError, "can't set"): + with self.assertRaises(AttributeError, msg="property 'send_bytes' of 'MeteredSocket' object has no setter"): socket.send_bytes = 0 - with self.assertRaisesRegex(AttributeError, "can't set"): + with self.assertRaises(AttributeError, msg="property 'recv_ops' of 'MeteredSocket' object has no setter"): socket.recv_ops = 0 - with self.assertRaisesRegex(AttributeError, "can't set"): + with self.assertRaises(AttributeError, msg="property 'recv_bytes' of 'MeteredSocket' object has no setter"): socket.recv_bytes = 0 self.assertEqual(278, socket.send_ops) self.assertEqual(282, socket.send_bytes) @@ -426,19 +426,15 @@ def test_meteredfile_stats_read_only(self, super_mock): file.write(b"bytes") self.assertEqual(78, file.write_ops) self.assertEqual(82, file.write_bytes) - with self.assertRaisesRegex(AttributeError, "can't set"): + with self.assertRaises(AttributeError, msg="property 'write_ops' of 'MeteredFile' object has no setter"): file.write_ops = 0 - with self.assertRaisesRegex(AttributeError, "can't set"): + with self.assertRaises(AttributeError, msg="property 'write_bytes' of 'MeteredFile' object has no setter"): file.write_bytes = 0 - with self.assertRaisesRegex(AttributeError, "can't set"): + with self.assertRaises(AttributeError, msg="property 'read_ops' of 'MeteredFile' object has no setter"): file.read_ops = 0 - with self.assertRaisesRegex(AttributeError, "can't set"): + with self.assertRaises(AttributeError, msg="property 'read_bytes' of 'MeteredFile' object has no setter"): file.read_bytes = 0 self.assertEqual(78, file.write_ops) self.assertEqual(82, file.write_bytes) self.assertEqual(58, file.read_ops) self.assertEqual(59, file.read_bytes) - - -if __name__ == "__main__": - unittest.main() diff --git a/requirements-generator.txt b/requirements-generator.txt index 3c096bebc1..d472d158b9 100644 --- a/requirements-generator.txt +++ b/requirements-generator.txt @@ -1,6 +1,6 @@ -black==22.3.0 +black<=22.3.0 flake8~=5.0.4 Jinja2~=3.1.2 python-dateutil==2.8.1 markupsafe==2.0.1 -tomli~=2.0.1 +tomli>=1.1.0; python_full_version < '3.11.2' diff --git a/requirements.txt b/requirements.txt index d8556f8d61..5b97def747 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ astroid<=2.12.0 flake8~=5.0.4 -pylint~=2.14.5 +pylint~=2.17.1 +black<=22.3.0 yapf~=0.32.0 -tomli~=2.0.1 \ No newline at end of file +tomli>=1.1.0; python_full_version < '3.11.2' \ No newline at end of file From 944f0b8a5e1e3f55e3e70b94325a0108cc15ad9b Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sun, 9 Apr 2023 14:15:01 +0200 Subject: [PATCH 393/826] Sync etl docs with problem-specifications The etl exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2250 --- exercises/practice/etl/.docs/instructions.md | 29 ++++++-------------- exercises/practice/etl/.docs/introduction.md | 16 +++++++++++ 2 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 exercises/practice/etl/.docs/introduction.md diff --git a/exercises/practice/etl/.docs/instructions.md b/exercises/practice/etl/.docs/instructions.md index fffe64f201..802863b540 100644 --- a/exercises/practice/etl/.docs/instructions.md +++ b/exercises/practice/etl/.docs/instructions.md @@ -1,19 +1,8 @@ # Instructions -We are going to do the `Transform` step of an Extract-Transform-Load. +Your task is to change the data format of letters and their point values in the game. -## ETL - -Extract-Transform-Load (ETL) is a fancy way of saying, "We have some crufty, legacy data over in this system, and now we need it in this shiny new system over here, so we're going to migrate this." - -(Typically, this is followed by, "We're only going to need to run this once." -That's then typically followed by much forehead slapping and moaning about how stupid we could possibly be.) - -## The goal - -We're going to extract some Scrabble scores from a legacy system. - -The old system stored a list of letters per score: +Currently, letters are stored in groups based on their score, in a one-to-many mapping. - 1 point: "A", "E", "I", "O", "U", "L", "N", "R", "S", "T", - 2 points: "D", "G", @@ -23,18 +12,16 @@ The old system stored a list of letters per score: - 8 points: "J", "X", - 10 points: "Q", "Z", -The shiny new Scrabble system instead stores the score per letter, which makes it much faster and easier to calculate the score for a word. -It also stores the letters in lower-case regardless of the case of the input letters: +This needs to be changed to store each individual letter with its score in a one-to-one mapping. - "a" is worth 1 point. - "b" is worth 3 points. - "c" is worth 3 points. - "d" is worth 2 points. -- Etc. - -Your mission, should you choose to accept it, is to transform the legacy data format to the shiny new format. +- etc. -## Notes +As part of this change, the team has also decided to change the letters to be lower-case rather than upper-case. -A final note about scoring, Scrabble is played around the world in a variety of languages, each with its own unique scoring table. -For example, an "E" is scored at 2 in the Māori-language version of the game while being scored at 4 in the Hawaiian-language version. +~~~~exercism/note +If you want to look at how the data was previously structured and how it needs to change, take a look at the examples in the test suite. +~~~~ diff --git a/exercises/practice/etl/.docs/introduction.md b/exercises/practice/etl/.docs/introduction.md new file mode 100644 index 0000000000..5be65147d7 --- /dev/null +++ b/exercises/practice/etl/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that makes an online multiplayer game called Lexiconia. + +To play the game, each player is given 13 letters, which they must rearrange to create words. +Different letters have different point values, since it's easier to create words with some letters than others. + +The game was originally launched in English, but it is very popular, and now the company wants to expand to other languages as well. + +Different languages need to support different point values for letters. +The point values are determined by how often letters are used, compared to other letters in that language. + +For example, the letter 'C' is quite common in English, and is only worth 3 points. +But in Norwegian it's a very rare letter, and is worth 10 points. + +To make it easier to add new languages, your team needs to change the way letters and their point values are stored in the game. From bff0b7e31d63b854a06475b8808cc9bb5eb3da22 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sun, 9 Apr 2023 16:43:54 +0200 Subject: [PATCH 394/826] Sync linked-list docs with problem-specifications The linked-list exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2245 --- .../linked-list/.docs/instructions.md | 34 +++++++++---------- .../linked-list/.docs/introduction.md | 6 ++++ 2 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 exercises/practice/linked-list/.docs/introduction.md diff --git a/exercises/practice/linked-list/.docs/instructions.md b/exercises/practice/linked-list/.docs/instructions.md index 3d949d3935..a47942d73d 100644 --- a/exercises/practice/linked-list/.docs/instructions.md +++ b/exercises/practice/linked-list/.docs/instructions.md @@ -1,26 +1,26 @@ # Instructions -Implement a doubly linked list. +Your team has decided to use a doubly linked list to represent each train route in the schedule. +Each station along the train's route will be represented by a node in the linked list. -Like an array, a linked list is a simple linear data structure. -Several common data types can be implemented using linked lists, like queues, stacks, and associative arrays. +You don't need to worry about arrival and departure times at the stations. +Each station will simply be represented by a number. -A linked list is a collection of data elements called *nodes*. -In a *singly linked list* each node holds a value and a link to the next node. -In a *doubly linked list* each node also holds a link to the previous node. +Routes can be extended, adding stations to the beginning or end of a route. +They can also be shortened by removing stations from the beginning or the end of a route. -You will write an implementation of a doubly linked list. -Implement a Node to hold a value and pointers to the next and previous nodes. -Then implement a List which holds references to the first and last node and offers an array-like interface for adding and removing items: +Sometimes a station gets closed down, and in that case the station needs to be removed from the route, even if it is not at the beginning or end of the route. -- `push` (*insert value at back*); -- `pop` (*remove value at back*); -- `shift` (*remove value at front*). -- `unshift` (*insert value at front*); +The size of a route is measured not by how far the train travels, but by how many stations it stops at. -To keep your implementation simple, the tests will not cover error conditions. -Specifically: `pop` or `shift` will never be called on an empty list. +```exercism/note +The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. +As the name suggests, it is a list of nodes that are linked together. +It is a list of "nodes", where each node links to its neighbor or neighbors. +In a **singly linked list** each node links only to the node that follows it. +In a **doubly linked list** each node links to both the node that comes before, as well as the node that comes after. -Read more about [linked lists on Wikipedia][linked-lists]. +If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. -[linked-lists]: https://en.wikipedia.org/wiki/Linked_list +[intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d +``` diff --git a/exercises/practice/linked-list/.docs/introduction.md b/exercises/practice/linked-list/.docs/introduction.md new file mode 100644 index 0000000000..6e83ae7b6e --- /dev/null +++ b/exercises/practice/linked-list/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +You are working on a project to develop a train scheduling system for a busy railway network. + +You've been asked to develop a prototype for the train routes in the scheduling system. +Each route consists of a sequence of train stations that a given train stops at. From 999321d6237463b974a2d717c78c76e06ed5c881 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sun, 9 Apr 2023 15:46:13 +0200 Subject: [PATCH 395/826] Sync word-count docs with problem-specifications The word-count exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2247 --- .../practice/word-count/.docs/instructions.md | 52 ++++++++++++------- .../practice/word-count/.docs/introduction.md | 8 +++ 2 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 exercises/practice/word-count/.docs/introduction.md diff --git a/exercises/practice/word-count/.docs/instructions.md b/exercises/practice/word-count/.docs/instructions.md index 8b7f03ede7..064393c8a0 100644 --- a/exercises/practice/word-count/.docs/instructions.md +++ b/exercises/practice/word-count/.docs/instructions.md @@ -1,31 +1,47 @@ # Instructions -Given a phrase, count the occurrences of each _word_ in that phrase. +Your task is to count how many times each word occurs in a subtitle of a drama. -For the purposes of this exercise you can expect that a _word_ will always be one of: +The subtitles from these dramas use only ASCII characters. -1. A _number_ composed of one or more ASCII digits (ie "0" or "1234") OR -2. A _simple word_ composed of one or more ASCII letters (ie "a" or "they") OR -3. A _contraction_ of two _simple words_ joined by a single apostrophe (ie "it's" or "they're") +The characters often speak in casual English, using contractions like _they're_ or _it's_. +Though these contractions come from two words (e.g. _we are_), the contraction (_we're_) is considered a single word. -When counting words you can assume the following rules: +Words can be separated by any form of punctuation (e.g. ":", "!", or "?") or whitespace (e.g. "\t", "\n", or " "). +The only punctuation that does not separate words is the apostrophe in contractions. -1. The count is _case insensitive_ (ie "You", "you", and "YOU" are 3 uses of the same word) -2. The count is _unordered_; the tests will ignore how words and counts are ordered -3. Other than the apostrophe in a _contraction_ all forms of _punctuation_ are regarded as spaces -4. The words can be separated by _any_ form of whitespace (ie "\t", "\n", " ") +Numbers are considered words. +If the subtitles say _It costs 100 dollars._ then _100_ will be its own word. -For example, for the phrase `"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` the count would be: +Words are case insensitive. +For example, the word _you_ occurs three times in the following sentence: + +> You come back, you hear me? DO YOU HEAR ME? + +The ordering of the word counts in the results doesn't matter. + +Here's an example that incorporates several of the elements discussed above: + +- simple words +- contractions +- numbers +- case insensitive words +- punctuation (including apostrophes) to separate words +- different forms of whitespace to separate words + +`"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` + +The mapping for this subtitle would be: ```text -that's: 1 -the: 2 -password: 2 123: 1 -cried: 1 -special: 1 agent: 1 -so: 1 -i: 1 +cried: 1 fled: 1 +i: 1 +password: 2 +so: 1 +special: 1 +that's: 1 +the: 2 ``` diff --git a/exercises/practice/word-count/.docs/introduction.md b/exercises/practice/word-count/.docs/introduction.md new file mode 100644 index 0000000000..1654508e79 --- /dev/null +++ b/exercises/practice/word-count/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You teach English as a foreign language to high school students. + +You've decided to base your entire curriculum on TV shows. +You need to analyze which words are used, and how often they're repeated. + +This will let you choose the simplest shows to start with, and to gradually increase the difficulty as time passes. From 7446286e8f3fe49d066478c9be7d3623c89e8ea8 Mon Sep 17 00:00:00 2001 From: HliebS <129861638+HliebS@users.noreply.github.com> Date: Mon, 10 Apr 2023 20:28:53 +0200 Subject: [PATCH 396/826] Fix English-Ukrainian translation typo (#3394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The provided translation was from English to Russian. In Ukrainian "nine" is written with an apostrophe - дев'ять. --- exercises/concept/little-sisters-vocab/.docs/introduction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/little-sisters-vocab/.docs/introduction.md b/exercises/concept/little-sisters-vocab/.docs/introduction.md index d7ccff84e6..220b9b73eb 100644 --- a/exercises/concept/little-sisters-vocab/.docs/introduction.md +++ b/exercises/concept/little-sisters-vocab/.docs/introduction.md @@ -36,13 +36,13 @@ Strings can be concatenated using the `+` operator. ```python language = "Ukrainian" number = "nine" -word = "девять" +word = "дев'ять" sentence = word + " " + "means" + " " + number + " in " + language + "." >>> print(sentence) ... -"девять means nine in Ukrainian." +"дев'ять means nine in Ukrainian." ``` If a `list`, `tuple`, `set` or other collection of individual strings needs to be combined into a single `str`, [`.join()`][str-join], is a better option: From cade613a31dd4df3cff5aed75b380d451f71043a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 10 Apr 2023 12:01:27 -0700 Subject: [PATCH 397/826] [Strings Concept] Update about.md to fix Ukrainian Example Per PR 3394, fixed the Ukraninian example in the concept `about.md`, to match the corrected on in the exercise `introduction.md`. --- concepts/strings/about.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/strings/about.md b/concepts/strings/about.md index 895bad45b4..0107f6e70f 100644 --- a/concepts/strings/about.md +++ b/concepts/strings/about.md @@ -159,13 +159,13 @@ This method should be used sparingly, as it is not very performant or easily mai ```python language = "Ukrainian" number = "nine" -word = "девять" +word = "дев'ять" sentence = word + " " + "means" + " " + number + " in " + language + "." >>> print(sentence) ... -"девять means nine in Ukrainian." +"дев'ять means nine in Ukrainian." ``` If a `list`, `tuple`, `set` or other collection of individual strings needs to be combined into a single `str`, [`.join()`][str-join], is a better option: From 869f0723275a2f7e32b89e05d9baf150f40b2275 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 10 Apr 2023 14:20:06 +0200 Subject: [PATCH 398/826] Sync sum-of-multiples docs with problem-specifications The sum-of-multiples exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2249 --- .../sum-of-multiples/.docs/instructions.md | 29 ++++++++++++------- .../sum-of-multiples/.docs/introduction.md | 6 ++++ 2 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 exercises/practice/sum-of-multiples/.docs/introduction.md diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index 7b7ec006e2..9c824bf1d5 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -1,18 +1,27 @@ # Instructions -Given a list of factors and a limit, add up all the unique multiples of the factors that are less than the limit. -All inputs will be greater than or equal to zero. +Your task is to write the code that calculates the energy points that get awarded to players when they complete a level. -## Example +The points awarded depend on two things: -Suppose the limit is 20 and the list of factors is [3, 5]. -We need to find the sum of all unique multiples of 3 and 5 that are less than 20. +- The level (a number) that the player completed. +- The base value of each magical item collected by the player during that level. -Multiples of 3 less than 20: 3, 6, 9, 12, 15, 18 -Multiples of 5 less than 20: 5, 10, 15 +The energy points are awarded according to the following rules: -The unique multiples are: 3, 5, 6, 9, 10, 12, 15, 18 +1. For each magical item, take the base value and find all the multiples of that value that are less than or equal to the level number. +2. Combine the sets of numbers. +3. Remove any duplicates. +4. Calculate the sum of all the numbers that are left. -The sum of the unique multiples is: 3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78 +Let's look an example: -So, the answer is 78. +**The player completed level 20 and found two magical items with base values of 3 and 5.** + +To calculate the energy points earned by the player, we need to find all the unique multiples of these base values that are less than or equal to level 20. + +- Multiples of 3 up to 20: `{3, 6, 9, 12, 15, 18}` +- Multiples of 5 up to 20: `{5, 10, 15, 20}` +- Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18, 20}` +- Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 + 20 = 98` +- Therefore, the player earns **98** energy points for completing level 20 and finding the two magical items with base values of 3 and 5. diff --git a/exercises/practice/sum-of-multiples/.docs/introduction.md b/exercises/practice/sum-of-multiples/.docs/introduction.md new file mode 100644 index 0000000000..69cabeed5a --- /dev/null +++ b/exercises/practice/sum-of-multiples/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +You work for a company that makes an online, fantasy-survival game. + +When a player finishes a level, they are awarded energy points. +The amount of energy awarded depends on which magical items the player found while exploring that level. From 914774cc9bc730450295c843fc9861c16f28800f Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 11 Apr 2023 20:31:05 +0200 Subject: [PATCH 399/826] sync sum of multiples docs (#3398) * Sync sum-of-multiples docs with problem-specifications The sum-of-multiples exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2249 * Sync sum-of-multiples docs with problem-specifications There were a few follow-on tweaks to sum-of-multiples. For context, this is part of the project to overhaul all the practice exercises. You can read about that here: https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 ---- If you approve this pull request, I will eventually merge it. However, if you are happy with this change **please merge the pull request**, as it will get the changes into the hands of the students much more quickly. If this pull request contradicts the exercise on your track, **please add a review with _request changes_**. This will block the pull request from getting merged. Otherwise we aim to take an optimistic merging approach. If you wish to suggest tweaks to these changes, please open a pull request to the exercism/problem-specifications repository to discuss, so that everyone who has an interest in the shared exercise descriptions can participate. * Update instructions.md Resolved merge conflicts, and am now attempting to re-push to repo. --------- Co-authored-by: BethanyG --- .../sum-of-multiples/.docs/instructions.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index 9c824bf1d5..e9a25636e5 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -7,21 +7,21 @@ The points awarded depend on two things: - The level (a number) that the player completed. - The base value of each magical item collected by the player during that level. -The energy points are awarded according to the following rules: +The energy points are awarded according to the following rules: -1. For each magical item, take the base value and find all the multiples of that value that are less than or equal to the level number. +1. For each magical item, take the base value and find all the multiples of that value that are less than the level number. 2. Combine the sets of numbers. 3. Remove any duplicates. -4. Calculate the sum of all the numbers that are left. +4. Calculate the sum of all the numbers that are left. -Let's look an example: +Let's look at an example: **The player completed level 20 and found two magical items with base values of 3 and 5.** -To calculate the energy points earned by the player, we need to find all the unique multiples of these base values that are less than or equal to level 20. +To calculate the energy points earned by the player, we need to find all the unique multiples of these base values that are less than level 20. -- Multiples of 3 up to 20: `{3, 6, 9, 12, 15, 18}` -- Multiples of 5 up to 20: `{5, 10, 15, 20}` -- Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18, 20}` -- Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 + 20 = 98` -- Therefore, the player earns **98** energy points for completing level 20 and finding the two magical items with base values of 3 and 5. +- Multiples of 3 less than 20: `{3, 6, 9, 12, 15, 18}` +- Multiples of 5 less than 20: `{5, 10, 15}` +- Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18}` +- Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78` +- Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5 From 8ae39796b7ebefb1cbca3d26461df3367466fbdf Mon Sep 17 00:00:00 2001 From: Al Momin Faruk <31950303+mominfaruk@users.noreply.github.com> Date: Wed, 12 Apr 2023 22:44:51 +0600 Subject: [PATCH 400/826] Replace stale link by working link with valuable informtion (#3399) * Replace stale link by working link with valuable informtion * Update concepts/tuples/links.json --------- Co-authored-by: BethanyG --- concepts/tuples/links.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/tuples/links.json b/concepts/tuples/links.json index a6a22b9b8f..a0d85de8a5 100644 --- a/concepts/tuples/links.json +++ b/concepts/tuples/links.json @@ -16,8 +16,8 @@ "description": "Lists vs Tuples" }, { - "url": "http://e-scribe.com/post/understanding-tuples-vs-lists-in-python/", - "description": "Understanding tuples vs lists in Python" + "url": "https://stackoverflow.com/a/626871", + "description": "What's the difference between lists and tuples?" }, { "url": "https://jtauber.com/blog/2006/04/15/python_tuples_are_not_just_constant_lists/", From aecf530858499c4fbb8dcaaab24baf26e11b0cc5 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 10 Apr 2023 18:48:26 -0700 Subject: [PATCH 401/826] Synced tests.toml for depricated exercises. --- .../practice/accumulate/.meta/tests.toml | 35 ++++++++++ .../.meta/tests.toml | 62 +++++++++++++++++ exercises/practice/strain/.meta/tests.toml | 66 +++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 exercises/practice/accumulate/.meta/tests.toml create mode 100644 exercises/practice/parallel-letter-frequency/.meta/tests.toml create mode 100644 exercises/practice/strain/.meta/tests.toml diff --git a/exercises/practice/accumulate/.meta/tests.toml b/exercises/practice/accumulate/.meta/tests.toml new file mode 100644 index 0000000000..150ec2e896 --- /dev/null +++ b/exercises/practice/accumulate/.meta/tests.toml @@ -0,0 +1,35 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[64d97c14-36dd-44a8-9621-2cecebd6ed23] +description = "accumulate empty" +include = false + +[00008ed2-4651-4929-8c08-8b4dbd70872e] +description = "accumulate squares" +include = false + +[551016da-4396-4cae-b0ec-4c3a1a264125] +description = "accumulate upcases" +include = false + +[cdf95597-b6ec-4eac-a838-3480d13d0d05] +description = "accumulate reversed strings" +include = false + +[bee8e9b6-b16f-4cd2-be3b-ccf7457e50bb] +description = "accumulate recursively" +include = false + +[0b357334-4cad-49e1-a741-425202edfc7c] +description = "accumulate recursively" +include = false +reimplements = "bee8e9b6-b16f-4cd2-be3b-ccf7457e50bb" diff --git a/exercises/practice/parallel-letter-frequency/.meta/tests.toml b/exercises/practice/parallel-letter-frequency/.meta/tests.toml new file mode 100644 index 0000000000..6cf36e6fd2 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.meta/tests.toml @@ -0,0 +1,62 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[c054d642-c1fa-4234-8007-9339f2337886] +description = "no texts" +include = false + +[818031be-49dc-4675-b2f9-c4047f638a2a] +description = "one text with one letter" +include = false + +[c0b81d1b-940d-4cea-9f49-8445c69c17ae] +description = "one text with multiple letters" +include = false + +[708ff1e0-f14a-43fd-adb5-e76750dcf108] +description = "two texts with one letter" +include = false + +[1b5c28bb-4619-4c9d-8db9-a4bb9c3bdca0] +description = "two texts with multiple letters" +include = false + +[6366e2b8-b84c-4334-a047-03a00a656d63] +description = "ignore letter casing" +include = false + +[92ebcbb0-9181-4421-a784-f6f5aa79f75b] +description = "ignore whitespace" +include = false + +[bc5f4203-00ce-4acc-a5fa-f7b865376fd9] +description = "ignore punctuation" +include = false + +[68032b8b-346b-4389-a380-e397618f6831] +description = "ignore numbers" +include = false + +[aa9f97ac-3961-4af1-88e7-6efed1bfddfd] +description = "Unicode letters" +include = false + +[7b1da046-701b-41fc-813e-dcfb5ee51813] +description = "combination of lower- and uppercase letters, punctuation and white space" +include = false + +[4727f020-df62-4dcf-99b2-a6e58319cb4f] +description = "large texts" +include = false + +[adf8e57b-8e54-4483-b6b8-8b32c115884c] +description = "many small texts" +include = false diff --git a/exercises/practice/strain/.meta/tests.toml b/exercises/practice/strain/.meta/tests.toml new file mode 100644 index 0000000000..66ed1044bd --- /dev/null +++ b/exercises/practice/strain/.meta/tests.toml @@ -0,0 +1,66 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[26af8c32-ba6a-4eb3-aa0a-ebd8f136e003] +description = "keep on empty list returns empty list" +include = false + +[f535cb4d-e99b-472a-bd52-9fa0ffccf454] +description = "keeps everything" +include = false + +[950b8e8e-f628-42a8-85e2-9b30f09cde38] +description = "keeps nothing" +include = false + +[92694259-6e76-470c-af87-156bdf75018a] +description = "keeps first and last" +include = false + +[938f7867-bfc7-449e-a21b-7b00cbb56994] +description = "keeps neither first nor last" +include = false + +[8908e351-4437-4d2b-a0f7-770811e48816] +description = "keeps strings" +include = false + +[2728036b-102a-4f1e-a3ef-eac6160d876a] +description = "keeps lists" +include = false + +[ef16beb9-8d84-451a-996a-14e80607fce6] +description = "discard on empty list returns empty list" +include = false + +[2f42f9bc-8e06-4afe-a222-051b5d8cd12a] +description = "discards everything" +include = false + +[ca990fdd-08c2-4f95-aa50-e0f5e1d6802b] +description = "discards nothing" +include = false + +[71595dae-d283-48ca-a52b-45fa96819d2f] +description = "discards first and last" +include = false + +[ae141f79-f86d-4567-b407-919eaca0f3dd] +description = "discards neither first nor last" +include = false + +[daf25b36-a59f-4f29-bcfe-302eb4e43609] +description = "discards strings" +include = false + +[a38d03f9-95ad-4459-80d1-48e937e4acaf] +description = "discards lists" +include = false From 218d2ba1cccab27cb64ccc8268e4ea88e938ae3f Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 12 Apr 2023 12:26:02 +0200 Subject: [PATCH 402/826] Sync rna-transcription docs with problem-specifications The rna-transcription exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2251 --- .../rna-transcription/.docs/instructions.md | 6 +++++- .../rna-transcription/.docs/introduction.md | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/rna-transcription/.docs/introduction.md diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index 851bdb49db..36da381f5a 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Given a DNA strand, return its RNA complement (per RNA transcription). +Your task is determine the RNA complement of a given DNA sequence. Both DNA and RNA strands are a sequence of nucleotides. @@ -14,3 +14,7 @@ Given a DNA strand, its transcribed RNA strand is formed by replacing each nucle - `C` -> `G` - `T` -> `A` - `A` -> `U` + +~~~~exercism/note +If you want to look at how the inputs and outputs are structured, take a look at the examples in the test suite. +~~~~ diff --git a/exercises/practice/rna-transcription/.docs/introduction.md b/exercises/practice/rna-transcription/.docs/introduction.md new file mode 100644 index 0000000000..6b3f44b532 --- /dev/null +++ b/exercises/practice/rna-transcription/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a bioengineering company that specializes in developing therapeutic solutions. + +Your team has just been given a new project to develop a targeted therapy for a rare type of cancer. + +~~~~exercism/note +It's all very complicated, but the basic idea is that sometimes people's bodies produce too much of a given protein. +That can cause all sorts of havoc. + +But if you can create a very specific molecule (called a micro-RNA), it can prevent the protein from being produced. + +This technique is called [RNA Interference][rnai]. + +[rnai]: https://admin.acceleratingscience.com/ask-a-scientist/what-is-rnai/ +~~~~ From 196cfa37ea729ca5306d494767def568e5bc09e6 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 12 Apr 2023 11:29:02 +0200 Subject: [PATCH 403/826] Sync largest-series-product docs with problem-specifications The largest-series-product exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2246 --- .../.docs/instructions.md | 28 +++++++++++++------ .../.docs/introduction.md | 5 ++++ 2 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 exercises/practice/largest-series-product/.docs/introduction.md diff --git a/exercises/practice/largest-series-product/.docs/instructions.md b/exercises/practice/largest-series-product/.docs/instructions.md index 08586dd593..f297b57f7c 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.md +++ b/exercises/practice/largest-series-product/.docs/instructions.md @@ -1,14 +1,26 @@ # Instructions -Given a string of digits, calculate the largest product for a contiguous substring of digits of length n. +Your task is to look for patterns in the long sequence of digits in the encrypted signal. -For example, for the input `'1027839564'`, the largest product for a series of 3 digits is 270 `(9 * 5 * 6)`, and the largest product for a series of 5 digits is 7560 `(7 * 8 * 3 * 9 * 5)`. +The technique you're going to use here is called the largest series product. -Note that these series are only required to occupy *adjacent positions* in the input; the digits need not be *numerically consecutive*. +Let's define a few terms, first. -For the input `'73167176531330624919225119674426574742355349194934'`, -the largest product for a series of 6 digits is 23520. +- **input**: the sequence of digits that you need to analyze +- **series**: a sequence of adjacent digits (those that are next to each other) that is contained within the input +- **span**: how many digits long each series is +- **product**: what you get when you multiply numbers together -For a series of zero digits, the largest product is 1 because 1 is the multiplicative identity. -(You don't need to know what a multiplicative identity is to solve this problem; -it just means that multiplying a number by 1 gives you the same number.) +Let's work through an example, with the input `"63915"`. + +- To form a series, take adjacent digits in the original input. +- If you are working with a span of `3`, there will be three possible series: + - `"639"` + - `"391"` + - `"915"` +- Then we need to calculate the product of each series: + - The product of the series `"639"` is 162 (`6 × 3 × 9 = 162`) + - The product of the series `"391"` is 27 (`3 × 9 × 1 = 27`) + - The product of the series `"915"` is 45 (`9 × 1 × 5 = 45`) +- 162 is bigger than both 27 and 45, so the largest series product of `"63915"` is from the series `"639"`. + So the answer is **162**. diff --git a/exercises/practice/largest-series-product/.docs/introduction.md b/exercises/practice/largest-series-product/.docs/introduction.md new file mode 100644 index 0000000000..597bb5fa15 --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a government agency that has intercepted a series of encrypted communication signals from a group of bank robbers. +The signals contain a long sequence of digits. +Your team needs to use various digital signal processing techniques to analyze the signals and identify any patterns that may indicate the planning of a heist. From 478b3b3fbecbfa4a6fefa34bba2e03e191e9bfb7 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 12 Apr 2023 11:29:18 +0200 Subject: [PATCH 404/826] Delete test cases from largest-series-product This deletes two deprecated test cases so that we can dramatically simplify the instructions for this exercise. --- exercises/practice/largest-series-product/.meta/tests.toml | 2 ++ .../largest-series-product/largest_series_product_test.py | 6 ------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/exercises/practice/largest-series-product/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml index 6c111adf0f..8831692597 100644 --- a/exercises/practice/largest-series-product/.meta/tests.toml +++ b/exercises/practice/largest-series-product/.meta/tests.toml @@ -41,9 +41,11 @@ description = "rejects span longer than string length" [06bc8b90-0c51-4c54-ac22-3ec3893a079e] description = "reports 1 for empty string and empty product (0 span)" +include = false [3ec0d92e-f2e2-4090-a380-70afee02f4c0] description = "reports 1 for nonempty string and empty product (0 span)" +include = false [6d96c691-4374-4404-80ee-2ea8f3613dd4] description = "rejects empty string and nonzero span" diff --git a/exercises/practice/largest-series-product/largest_series_product_test.py b/exercises/practice/largest-series-product/largest_series_product_test.py index 0a67d4ca14..ba8ae10dc8 100644 --- a/exercises/practice/largest-series-product/largest_series_product_test.py +++ b/exercises/practice/largest-series-product/largest_series_product_test.py @@ -46,12 +46,6 @@ def test_rejects_span_longer_than_string_length(self): err.exception.args[0], "span must be smaller than string length" ) - def test_reports_1_for_empty_string_and_empty_product_0_span(self): - self.assertEqual(largest_product("", 0), 1) - - def test_reports_1_for_nonempty_string_and_empty_product_0_span(self): - self.assertEqual(largest_product("123", 0), 1) - def test_rejects_empty_string_and_nonzero_span(self): with self.assertRaises(ValueError) as err: largest_product("", 1) From 17d4f7b5ea7b5e984fd9b419913d44c28c02e82a Mon Sep 17 00:00:00 2001 From: Matthijs <19817960+MatthijsBlom@users.noreply.github.com> Date: Thu, 6 Apr 2023 20:18:26 +0200 Subject: [PATCH 405/826] Synchronize example code with tests --- .../largest-series-product/.docs/instructions.append.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/largest-series-product/.docs/instructions.append.md b/exercises/practice/largest-series-product/.docs/instructions.append.md index 9250db2e8c..5a0f9b9206 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.append.md +++ b/exercises/practice/largest-series-product/.docs/instructions.append.md @@ -12,8 +12,8 @@ To raise a `ValueError` with a message, write the message as an argument to the # span of numbers is longer than number series raise ValueError("span must be smaller than string length") -# span of number is zero or negative -raise ValueError("span must be greater than zero") +# span of number is negative +raise ValueError("span must not be negative") # series includes non-number input raise ValueError("digits input must only contain digits") From f333803a543ceb89a01239a0bbad1bae865dc73b Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Thu, 13 Apr 2023 12:50:41 +0200 Subject: [PATCH 406/826] Sync saddle-points docs with problem-specifications The saddle-points exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 --- .../saddle-points/.docs/instructions.md | 31 +++++++++---------- .../saddle-points/.docs/introduction.md | 9 ++++++ 2 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 exercises/practice/saddle-points/.docs/introduction.md diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index 920ecffed9..d861388e43 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -1,25 +1,24 @@ # Instructions -Detect saddle points in a matrix. +Your task is to find the potential trees where you could build your tree house. -So say you have a matrix like so: +The data company provides the data as grids that show the heights of the trees. +The rows of the grid represent the east-west direction, and the columns represent the north-south direction. -```text - 1 2 3 - |--------- -1 | 9 8 7 -2 | 5 3 2 <--- saddle point at row 2, column 1, with value 5 -3 | 6 6 7 -``` +An acceptable tree will be the the largest in its row, while being the smallest in its column. -It has a saddle point at row 2, column 1. +A grid might not have any good trees at all. +Or it might have one, or even several. -It's called a "saddle point" because it is greater than or equal to every element in its row and less than or equal to every element in its column. +Here is a grid that has exactly one candidate tree. -A matrix may have zero or more saddle points. + 1 2 3 4 + |----------- +1 | 9 8 7 8 +2 | 5 3 2 4 <--- potential tree house at row 2, column 1, for tree with height 5 +3 | 6 6 7 1 -Your code should be able to provide the (possibly empty) list of all the saddle points for any given matrix. +- Row 2 has values 5, 3, and 1. The largest value is 5. +- Column 1 has values 9, 5, and 6. The smallest value is 5. -The matrix can have a different number of rows and columns (Non square). - -Note that you may find other definitions of matrix saddle points online, but the tests for this exercise follow the above unambiguous definition. +So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md new file mode 100644 index 0000000000..b582efbd21 --- /dev/null +++ b/exercises/practice/saddle-points/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +You are planning on building a tree house in the woods near your house so that you can watch the sun rise and set. + +You've obtained data from a local survey company that shows the heights of all the trees in each rectangular section of the map. +You need to analyze each grid on the map to find the perfect tree for your tree house. + +The best tree will be the tallest tree compared to all the other trees to the east and west, so that you have the best possible view of the sunrises and sunsets. +You don't like climbing too much, so the perfect tree will also be the shortest among all the trees to the north and to the south. From c652ff3089fc253f62ed2b76a68f55a0937766a6 Mon Sep 17 00:00:00 2001 From: Meatball Date: Mon, 19 Dec 2022 09:27:16 +0100 Subject: [PATCH 407/826] Updated bank account --- bin/githelp.py | 2 +- .../practice/bank-account/.meta/template.j2 | 47 +++ .../practice/bank-account/.meta/tests.toml | 43 +++ .../bank-account/bank_account_test.py | 89 ++---- .../practice/bank-account/canonical_data.json | 285 ++++++++++++++++++ 5 files changed, 397 insertions(+), 69 deletions(-) create mode 100644 exercises/practice/bank-account/.meta/template.j2 create mode 100644 exercises/practice/bank-account/.meta/tests.toml create mode 100644 exercises/practice/bank-account/canonical_data.json diff --git a/bin/githelp.py b/bin/githelp.py index 3d3be06f66..9710e0c136 100644 --- a/bin/githelp.py +++ b/bin/githelp.py @@ -6,7 +6,7 @@ from typing import Iterator, Union -GITHUB_EXERCISM = f"https://github.com/exercism" +GITHUB_EXERCISM = f"https://github.com/meatball133" class Repo(Enum): diff --git a/exercises/practice/bank-account/.meta/template.j2 b/exercises/practice/bank-account/.meta/template.j2 new file mode 100644 index 0000000000..99c21bc8f6 --- /dev/null +++ b/exercises/practice/bank-account/.meta/template.j2 @@ -0,0 +1,47 @@ +{%- import "generator_macros.j2" as macros with context -%} +{% macro test_case(case) -%} + def test_{{ case["description"] | to_snake }}(self): + account = BankAccount() + {%- set inputs = case["input"]["operations"] -%} + {%- if case["expected"] -%} + {%- set error_case = true -%} + {%- set error_msg = case["expected"]["error"] -%} + {%- set error_operation = inputs[-1]["operation"] -%} + {%- set final_value = inputs[-1]["value"] -%} + {% endif %} + {%- if case["expected"] and inputs|length > 1 -%} + {%- set inputs = inputs[:-1] -%} + {%- endif -%} + {%- for input in inputs -%} + {%- set operation = input["operation"] -%} + {%- set value = input["value"] -%} + {%- set expected = input["expected"] %} + {%- if operation and value %} + account.{{ operation }}({{ value }}) + {%- elif operation == "amount" %} + self.assertEqual(account.get_balance(), {{ expected }}) + {%- elif operation and not value %} + account.{{ operation }}() + {%- endif %} + {%- endfor %} + {%- if error_case %} + with self.assertRaises(ValueError) as err: + account.{{ error_operation }}({{ final_value if final_value else "" }}) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ error_msg }}") + {%- endif %} +{% endmacro %} + +{{ macros.header(["BankAccount"]) }} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + {{ test_case(case) }} + {% endfor %} + {% if additional_cases | length -%} + + # Additional tests for this track + {% for case in additional_cases -%} + {{ test_case(case) }} + {% endfor %} + {%- endif %} diff --git a/exercises/practice/bank-account/.meta/tests.toml b/exercises/practice/bank-account/.meta/tests.toml new file mode 100644 index 0000000000..6778e39f45 --- /dev/null +++ b/exercises/practice/bank-account/.meta/tests.toml @@ -0,0 +1,43 @@ +# This is an auto-generated file. Regular comments will be removed when this +# file is regenerated. Regenerating will not touch any manually added keys, +# so comments can be added in a "comment" key. + +[983a1528-4ceb-45e5-8257-8ce01aceb5ed] +description = "encode yes" + +[e88d4ec3-c6bf-4752-8e59-5046c44e3ba7] +description = "encode no" + +[08f1af07-27ae-4b38-aa19-770bde558064] +description = "encode OMG" + +[6f6d242f-8c31-4ac6-8995-a90d42cad59f] +description = "encode spaces" + +[f9facfaa-d824-486e-8381-48832c4bbffd] +description = "encode mindblowingly" + +[7a65ba52-e35c-4fd2-8159-bda2bde6e59c] +description = "encode numbers" + +[570dfaa5-0532-4c1f-a7d3-0f65c3265608] +description = "encode deep thought" + +[c396d233-1c49-4272-98dc-7f502dbb9470] +description = "encode all the letters" + +[c06f534f-bdc2-4a02-a388-1063400684de] +description = "decode exercism" + +[0722d404-6116-4f92-ba3b-da7f88f1669c] +description = "decode a sentence" + +[ec42245f-9361-4341-8231-a22e8d19c52f] +description = "decode numbers" + +[4f381ef8-10ef-4507-8e1d-0631ecc8ee72] +description = "decode all the letters" + +[d45df9ea-1db0-47f3-b18c-d365db49d938] +description = "decode with too many spaces" + diff --git a/exercises/practice/bank-account/bank_account_test.py b/exercises/practice/bank-account/bank_account_test.py index 3a36b3ccb4..5a50505010 100644 --- a/exercises/practice/bank-account/bank_account_test.py +++ b/exercises/practice/bank-account/bank_account_test.py @@ -1,87 +1,75 @@ -import sys -import threading -import time import unittest -from bank_account import BankAccount +from bank_account import ( + BankAccount, +) + +# Tests adapted from `problem-specifications//canonical-data.json` class BankAccountTest(unittest.TestCase): - def test_newly_opened_account_has_zero_balance(self): + def test_using_pop_raises_an_error_if_the_list_is_empty(self): account = BankAccount() account.open() self.assertEqual(account.get_balance(), 0) - def test_can_deposit_money(self): + def test_can_return_with_pop_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() account.deposit(100) self.assertEqual(account.get_balance(), 100) - def test_can_deposit_money_sequentially(self): - account = BankAccount() - account.open() - account.deposit(100) - account.deposit(50) - - self.assertEqual(account.get_balance(), 150) - - def test_can_withdraw_money(self): + def test_using_shift_raises_an_error_if_the_list_is_empty(self): account = BankAccount() account.open() account.deposit(100) account.withdraw(50) - self.assertEqual(account.get_balance(), 50) - def test_can_withdraw_money_sequentially(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() account.deposit(100) - account.withdraw(20) account.withdraw(80) - + account.withdraw(20) self.assertEqual(account.get_balance(), 0) - def test_checking_balance_of_closed_account_throws_error(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() account.close() - with self.assertRaises(ValueError) as err: - account.get_balance() + account.amount() self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account not open") - def test_deposit_into_closed_account(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() account.close() - with self.assertRaises(ValueError) as err: account.deposit(50) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account not open") - - def test_withdraw_from_closed_account(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() account.close() - with self.assertRaises(ValueError) as err: account.withdraw(50) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account not open") - def test_close_already_closed_account(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() + account.close() with self.assertRaises(ValueError) as err: account.close() self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account not open") - def test_open_already_opened_account(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() with self.assertRaises(ValueError) as err: @@ -89,7 +77,7 @@ def test_open_already_opened_account(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account already open") - def test_reopened_account_does_not_retain_balance(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() account.deposit(50) @@ -97,63 +85,28 @@ def test_reopened_account_does_not_retain_balance(self): account.open() self.assertEqual(account.get_balance(), 0) - def test_cannot_withdraw_more_than_deposited(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() account.deposit(25) - with self.assertRaises(ValueError) as err: account.withdraw(50) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "amount must be less than balance") - def test_cannot_withdraw_negative(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() account.deposit(100) - with self.assertRaises(ValueError) as err: account.withdraw(-50) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "amount must be greater than 0") - def test_cannot_deposit_negative(self): + def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account = BankAccount() account.open() - with self.assertRaises(ValueError) as err: account.deposit(-50) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "amount must be greater than 0") - - def test_can_handle_concurrent_transactions(self): - account = BankAccount() - account.open() - account.deposit(1000) - - self.adjust_balance_concurrently(account) - - self.assertEqual(account.get_balance(), 1000) - - def adjust_balance_concurrently(self, account): - def transact(): - account.deposit(5) - time.sleep(0.001) - account.withdraw(5) - - # Greatly improve the chance of an operation being interrupted - # by thread switch, thus testing synchronization effectively - try: - sys.setswitchinterval(1e-12) - except AttributeError: - # For Python 2 compatibility - sys.setcheckinterval(1) - - threads = [threading.Thread(target=transact) for _ in range(1000)] - for thread in threads: - thread.start() - for thread in threads: - thread.join() - -if __name__ == '__main__': - unittest.main() diff --git a/exercises/practice/bank-account/canonical_data.json b/exercises/practice/bank-account/canonical_data.json new file mode 100644 index 0000000000..21e868eac9 --- /dev/null +++ b/exercises/practice/bank-account/canonical_data.json @@ -0,0 +1,285 @@ +{ + "exercise": "bank-account", + "comments": [ + "In order to keep the interface for the exercise close or equal to the ", + "description.md and also satisfying the canonical-data schema, the only ", + "properties are pop, push, shift, unshift. Some tracks also implement ", + "delete and count, via Track-Inserted hints. ", + " ", + "It is hard to have interesting tests using the property based approach so", + "this canonical data uses the following approach: ", + "- Each test input is an array of { operation, value?, expected? } ", + "- If operation is push, unshift, delete, then value is given ", + "- If operation is pop, shift, count, the expected value can be given ", + " ", + "Encoding tests and operations using the same field is necessary to have ", + "tests that don't just initialize the list, and then have one operation. " + ], + "cases": [ + { + "uuid": "983a1528-4ceb-45e5-8257-8ce01aceb5ed", + "description": "Using pop raises an error if the list is empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "amount", + "value": 0 + } + ] + }, "expected": {} + }, + { + "uuid": "e88d4ec3-c6bf-4752-8e59-5046c44e3ba7", + "description" : "Can return with pop and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "deposit", + "value": 100 + }, + { + "operation": "amount", + "expected": 100 + } + ] + }, + "expected": {} + }, + { + "uuid": "08f1af07-27ae-4b38-aa19-770bde558064", + "description": "Using shift raises an error if the list is empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "deposit", + "value": 100 + }, + { + "operation": "withdraw", + "value": 50 + }, + { + "operation": "amount", + "value": 50 + } + ] + }, + "expected": {} + }, + { + "uuid": "6f6d242f-8c31-4ac6-8995-a90d42cad59f", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "deposit", + "value": 100 + }, + { + "operation": "withdraw", + "value": 80 + }, + { + "operation": "withdraw", + "value": 20 + }, + { + "operation": "amount", + "value": 0 + } + ] + }, + "expected": {} + }, + { + "uuid": "f9facfaa-d824-486e-8381-48832c4bbffd", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "close" + }, + { + "operation": "amount" + } + ] + }, + "expected": {"error": "account not open"} + }, + { + "uuid": "7a65ba52-e35c-4fd2-8159-bda2bde6e59c", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "close" + }, + { + "operation": "deposit", + "value": 50 + } + ] + }, + "expected": {"error": "account not open"} + }, + { + "uuid": "570dfaa5-0532-4c1f-a7d3-0f65c3265608", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "close" + }, + { + "operation": "withdraw", + "value": 50 + } + ] + }, + "expected": {"error": "account not open"} + }, + { + "uuid": "c396d233-1c49-4272-98dc-7f502dbb9470", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "close" + } + ] + }, + "expected": {"error": "account not open"} + }, + { + "uuid": "c06f534f-bdc2-4a02-a388-1063400684de", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "open" + } + ] + }, + "expected": {"error": "account already open"} + }, + { + "uuid": "0722d404-6116-4f92-ba3b-da7f88f1669c", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "deposit", + "value": 50 + }, + { + "operation": "close" + }, + { + "operation": "open" + }, + { + "operation": "amount", + "value": 0 + } + ] + }, + "expected": {} + }, + { + "uuid": "ec42245f-9361-4341-8231-a22e8d19c52f", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "deposit", + "value": 25 + }, + { + "operation": "withdraw", + "value": 50 + } + ] + }, + "expected": {"error": "amount must be less than balance"} + }, + { + "uuid": "4f381ef8-10ef-4507-8e1d-0631ecc8ee72", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "deposit", + "value": 100 + }, + { + "operation": "withdraw", + "value": -50 + } + ] + }, + "expected": {"error": "amount must be greater than 0"} + }, + { + "uuid": "d45df9ea-1db0-47f3-b18c-d365db49d938", + "description": "Can return with shift and then raise an error if empty", + "property": "bankAccont", + "input": { + "operations": [ + { + "operation": "open" + }, + { + "operation": "deposit", + "value": -50 + } + ] + }, + "expected": {"error": "amount must be greater than 0"} + } +] +} \ No newline at end of file From 9bf39ef617a1960b393643bbe5cfc8933c56573e Mon Sep 17 00:00:00 2001 From: Meatball Date: Mon, 19 Dec 2022 09:38:24 +0100 Subject: [PATCH 408/826] fix --- exercises/practice/bank-account/.meta/template.j2 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/practice/bank-account/.meta/template.j2 b/exercises/practice/bank-account/.meta/template.j2 index 99c21bc8f6..3e2980484b 100644 --- a/exercises/practice/bank-account/.meta/template.j2 +++ b/exercises/practice/bank-account/.meta/template.j2 @@ -11,6 +11,8 @@ {% endif %} {%- if case["expected"] and inputs|length > 1 -%} {%- set inputs = inputs[:-1] -%} + {%- elif case["expected"] -%} + {%- set inputs = [] -%} {%- endif -%} {%- for input in inputs -%} {%- set operation = input["operation"] -%} From f95892f4be9b5c896901571befd4bb26ca0b4f32 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Apr 2023 19:07:29 -0700 Subject: [PATCH 409/826] Re-worked PR for Newly Accepted Canonical Data - Removed and regenerated `tests.toml` - Removed and regenerated `bank_account_test.py` - Re-worked the JinJa2 template - Removed the custom `canonical_data.json` -Removed the `bin/githelp.py` workaround - Updated `example.py` - Added meatball and bethanyg as contributors --- bin/githelp.py | 2 +- .../practice/bank-account/.meta/config.json | 4 +- .../practice/bank-account/.meta/example.py | 2 +- .../practice/bank-account/.meta/template.j2 | 42 +-- .../practice/bank-account/.meta/tests.toml | 51 +++- .../bank-account/bank_account_test.py | 57 ++-- .../practice/bank-account/canonical_data.json | 285 ------------------ 7 files changed, 102 insertions(+), 341 deletions(-) delete mode 100644 exercises/practice/bank-account/canonical_data.json diff --git a/bin/githelp.py b/bin/githelp.py index 9710e0c136..3d3be06f66 100644 --- a/bin/githelp.py +++ b/bin/githelp.py @@ -6,7 +6,7 @@ from typing import Iterator, Union -GITHUB_EXERCISM = f"https://github.com/meatball133" +GITHUB_EXERCISM = f"https://github.com/exercism" class Repo(Enum): diff --git a/exercises/practice/bank-account/.meta/config.json b/exercises/practice/bank-account/.meta/config.json index 8ab9aa0d88..51483ecb66 100644 --- a/exercises/practice/bank-account/.meta/config.json +++ b/exercises/practice/bank-account/.meta/config.json @@ -3,7 +3,9 @@ "contributors": [ "cmccandless", "Dog", - "tqa236" + "tqa236", + "bethanyg", + "meatball133" ], "files": { "solution": [ diff --git a/exercises/practice/bank-account/.meta/example.py b/exercises/practice/bank-account/.meta/example.py index 21248fa925..90ddf31c23 100644 --- a/exercises/practice/bank-account/.meta/example.py +++ b/exercises/practice/bank-account/.meta/example.py @@ -4,7 +4,7 @@ class BankAccount: def __init__(self): self.is_open = False - self.balance = 0 + self.balance = None self.lock = threading.Lock() def get_balance(self): diff --git a/exercises/practice/bank-account/.meta/template.j2 b/exercises/practice/bank-account/.meta/template.j2 index 3e2980484b..f077837a0f 100644 --- a/exercises/practice/bank-account/.meta/template.j2 +++ b/exercises/practice/bank-account/.meta/template.j2 @@ -3,47 +3,49 @@ def test_{{ case["description"] | to_snake }}(self): account = BankAccount() {%- set inputs = case["input"]["operations"] -%} - {%- if case["expected"] -%} + {%- if case["expected"]["error"] -%} {%- set error_case = true -%} {%- set error_msg = case["expected"]["error"] -%} {%- set error_operation = inputs[-1]["operation"] -%} - {%- set final_value = inputs[-1]["value"] -%} - {% endif %} - {%- if case["expected"] and inputs|length > 1 -%} + {%- set final_value = inputs[-1]["amount"] -%} {%- set inputs = inputs[:-1] -%} - {%- elif case["expected"] -%} - {%- set inputs = [] -%} - {%- endif -%} + {% endif %} {%- for input in inputs -%} {%- set operation = input["operation"] -%} - {%- set value = input["value"] -%} - {%- set expected = input["expected"] %} + {%- set value = input["amount"] -%} + {%- set expected = case["expected"] %} {%- if operation and value %} account.{{ operation }}({{ value }}) - {%- elif operation == "amount" %} + {%- elif not value and operation %} + {%- if not error_case and operation == "balance" %} self.assertEqual(account.get_balance(), {{ expected }}) - {%- elif operation and not value %} + {%- else %} account.{{ operation }}() - {%- endif %} - {%- endfor %} - {%- if error_case %} + {%- endif %} + {%- endif %} + {%- endfor %} + {%- if error_case %} with self.assertRaises(ValueError) as err: + {%- if error_operation == "balance" %} + account.get_balance({{ final_value if final_value else "" }}) + {%- else %} account.{{ error_operation }}({{ final_value if final_value else "" }}) + {%- endif %} self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "{{ error_msg }}") - {%- endif %} + {%- endif %} {% endmacro %} {{ macros.header(["BankAccount"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} - {{ test_case(case) }} + {{ test_case(case) }} {% endfor %} {% if additional_cases | length -%} - # Additional tests for this track - {% for case in additional_cases -%} - {{ test_case(case) }} - {% endfor %} + # Additional tests for this track + {% for case in additional_cases -%} + {{ test_case(case) }} + {% endfor %} {%- endif %} diff --git a/exercises/practice/bank-account/.meta/tests.toml b/exercises/practice/bank-account/.meta/tests.toml index 6778e39f45..1704a08c5a 100644 --- a/exercises/practice/bank-account/.meta/tests.toml +++ b/exercises/practice/bank-account/.meta/tests.toml @@ -1,43 +1,62 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [983a1528-4ceb-45e5-8257-8ce01aceb5ed] -description = "encode yes" +description = "Newly opened account has zero balance" [e88d4ec3-c6bf-4752-8e59-5046c44e3ba7] -description = "encode no" +description = "Single deposit" + +[3d9147d4-63f4-4844-8d2b-1fee2e9a2a0d] +description = "Multiple deposits" [08f1af07-27ae-4b38-aa19-770bde558064] -description = "encode OMG" +description = "Withdraw once" [6f6d242f-8c31-4ac6-8995-a90d42cad59f] -description = "encode spaces" +description = "Withdraw twice" + +[45161c94-a094-4c77-9cec-998b70429bda] +description = "Can do multiple operations sequentially" [f9facfaa-d824-486e-8381-48832c4bbffd] -description = "encode mindblowingly" +description = "Cannot check balance of closed account" [7a65ba52-e35c-4fd2-8159-bda2bde6e59c] -description = "encode numbers" +description = "Cannot deposit into closed account" + +[a0a1835d-faae-4ad4-a6f3-1fcc2121380b] +description = "Cannot deposit into unopened account" [570dfaa5-0532-4c1f-a7d3-0f65c3265608] -description = "encode deep thought" +description = "Cannot withdraw from closed account" [c396d233-1c49-4272-98dc-7f502dbb9470] -description = "encode all the letters" +description = "Cannot close an account that was not opened" [c06f534f-bdc2-4a02-a388-1063400684de] -description = "decode exercism" +description = "Cannot open an already opened account" [0722d404-6116-4f92-ba3b-da7f88f1669c] -description = "decode a sentence" +description = "Reopened account does not retain balance" [ec42245f-9361-4341-8231-a22e8d19c52f] -description = "decode numbers" +description = "Cannot withdraw more than deposited" [4f381ef8-10ef-4507-8e1d-0631ecc8ee72] -description = "decode all the letters" +description = "Cannot withdraw negative" [d45df9ea-1db0-47f3-b18c-d365db49d938] -description = "decode with too many spaces" +description = "Cannot deposit negative" +[ba0c1e0b-0f00-416f-8097-a7dfc97871ff] +description = "Can handle concurrent transactions" +include = false diff --git a/exercises/practice/bank-account/bank_account_test.py b/exercises/practice/bank-account/bank_account_test.py index 5a50505010..76ddbfe404 100644 --- a/exercises/practice/bank-account/bank_account_test.py +++ b/exercises/practice/bank-account/bank_account_test.py @@ -8,25 +8,32 @@ class BankAccountTest(unittest.TestCase): - def test_using_pop_raises_an_error_if_the_list_is_empty(self): + def test_newly_opened_account_has_zero_balance(self): account = BankAccount() account.open() self.assertEqual(account.get_balance(), 0) - def test_can_return_with_pop_and_then_raise_an_error_if_empty(self): + def test_single_deposit(self): account = BankAccount() account.open() account.deposit(100) self.assertEqual(account.get_balance(), 100) - def test_using_shift_raises_an_error_if_the_list_is_empty(self): + def test_multiple_deposits(self): account = BankAccount() account.open() account.deposit(100) - account.withdraw(50) - self.assertEqual(account.get_balance(), 50) + account.deposit(50) + self.assertEqual(account.get_balance(), 150) - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_withdraw_once(self): + account = BankAccount() + account.open() + account.deposit(100) + account.withdraw(75) + self.assertEqual(account.get_balance(), 25) + + def test_withdraw_twice(self): account = BankAccount() account.open() account.deposit(100) @@ -34,16 +41,26 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account.withdraw(20) self.assertEqual(account.get_balance(), 0) - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_can_do_multiple_operations_sequentially(self): + account = BankAccount() + account.open() + account.deposit(100) + account.deposit(110) + account.withdraw(200) + account.deposit(60) + account.withdraw(50) + self.assertEqual(account.get_balance(), 20) + + def test_cannot_check_balance_of_closed_account(self): account = BankAccount() account.open() account.close() with self.assertRaises(ValueError) as err: - account.amount() + account.get_balance() self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account not open") - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_cannot_deposit_into_closed_account(self): account = BankAccount() account.open() account.close() @@ -52,7 +69,14 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account not open") - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_cannot_deposit_into_unopened_account(self): + account = BankAccount() + with self.assertRaises(ValueError) as err: + account.deposit(50) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "account not open") + + def test_cannot_withdraw_from_closed_account(self): account = BankAccount() account.open() account.close() @@ -61,15 +85,14 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account not open") - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_cannot_close_an_account_that_was_not_opened(self): account = BankAccount() - account.close() with self.assertRaises(ValueError) as err: account.close() self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account not open") - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_cannot_open_an_already_opened_account(self): account = BankAccount() account.open() with self.assertRaises(ValueError) as err: @@ -77,7 +100,7 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "account already open") - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_reopened_account_does_not_retain_balance(self): account = BankAccount() account.open() account.deposit(50) @@ -85,7 +108,7 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): account.open() self.assertEqual(account.get_balance(), 0) - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_cannot_withdraw_more_than_deposited(self): account = BankAccount() account.open() account.deposit(25) @@ -94,7 +117,7 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "amount must be less than balance") - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_cannot_withdraw_negative(self): account = BankAccount() account.open() account.deposit(100) @@ -103,7 +126,7 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "amount must be greater than 0") - def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): + def test_cannot_deposit_negative(self): account = BankAccount() account.open() with self.assertRaises(ValueError) as err: diff --git a/exercises/practice/bank-account/canonical_data.json b/exercises/practice/bank-account/canonical_data.json deleted file mode 100644 index 21e868eac9..0000000000 --- a/exercises/practice/bank-account/canonical_data.json +++ /dev/null @@ -1,285 +0,0 @@ -{ - "exercise": "bank-account", - "comments": [ - "In order to keep the interface for the exercise close or equal to the ", - "description.md and also satisfying the canonical-data schema, the only ", - "properties are pop, push, shift, unshift. Some tracks also implement ", - "delete and count, via Track-Inserted hints. ", - " ", - "It is hard to have interesting tests using the property based approach so", - "this canonical data uses the following approach: ", - "- Each test input is an array of { operation, value?, expected? } ", - "- If operation is push, unshift, delete, then value is given ", - "- If operation is pop, shift, count, the expected value can be given ", - " ", - "Encoding tests and operations using the same field is necessary to have ", - "tests that don't just initialize the list, and then have one operation. " - ], - "cases": [ - { - "uuid": "983a1528-4ceb-45e5-8257-8ce01aceb5ed", - "description": "Using pop raises an error if the list is empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "amount", - "value": 0 - } - ] - }, "expected": {} - }, - { - "uuid": "e88d4ec3-c6bf-4752-8e59-5046c44e3ba7", - "description" : "Can return with pop and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "deposit", - "value": 100 - }, - { - "operation": "amount", - "expected": 100 - } - ] - }, - "expected": {} - }, - { - "uuid": "08f1af07-27ae-4b38-aa19-770bde558064", - "description": "Using shift raises an error if the list is empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "deposit", - "value": 100 - }, - { - "operation": "withdraw", - "value": 50 - }, - { - "operation": "amount", - "value": 50 - } - ] - }, - "expected": {} - }, - { - "uuid": "6f6d242f-8c31-4ac6-8995-a90d42cad59f", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "deposit", - "value": 100 - }, - { - "operation": "withdraw", - "value": 80 - }, - { - "operation": "withdraw", - "value": 20 - }, - { - "operation": "amount", - "value": 0 - } - ] - }, - "expected": {} - }, - { - "uuid": "f9facfaa-d824-486e-8381-48832c4bbffd", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "close" - }, - { - "operation": "amount" - } - ] - }, - "expected": {"error": "account not open"} - }, - { - "uuid": "7a65ba52-e35c-4fd2-8159-bda2bde6e59c", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "close" - }, - { - "operation": "deposit", - "value": 50 - } - ] - }, - "expected": {"error": "account not open"} - }, - { - "uuid": "570dfaa5-0532-4c1f-a7d3-0f65c3265608", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "close" - }, - { - "operation": "withdraw", - "value": 50 - } - ] - }, - "expected": {"error": "account not open"} - }, - { - "uuid": "c396d233-1c49-4272-98dc-7f502dbb9470", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "close" - } - ] - }, - "expected": {"error": "account not open"} - }, - { - "uuid": "c06f534f-bdc2-4a02-a388-1063400684de", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "open" - } - ] - }, - "expected": {"error": "account already open"} - }, - { - "uuid": "0722d404-6116-4f92-ba3b-da7f88f1669c", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "deposit", - "value": 50 - }, - { - "operation": "close" - }, - { - "operation": "open" - }, - { - "operation": "amount", - "value": 0 - } - ] - }, - "expected": {} - }, - { - "uuid": "ec42245f-9361-4341-8231-a22e8d19c52f", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "deposit", - "value": 25 - }, - { - "operation": "withdraw", - "value": 50 - } - ] - }, - "expected": {"error": "amount must be less than balance"} - }, - { - "uuid": "4f381ef8-10ef-4507-8e1d-0631ecc8ee72", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "deposit", - "value": 100 - }, - { - "operation": "withdraw", - "value": -50 - } - ] - }, - "expected": {"error": "amount must be greater than 0"} - }, - { - "uuid": "d45df9ea-1db0-47f3-b18c-d365db49d938", - "description": "Can return with shift and then raise an error if empty", - "property": "bankAccont", - "input": { - "operations": [ - { - "operation": "open" - }, - { - "operation": "deposit", - "value": -50 - } - ] - }, - "expected": {"error": "amount must be greater than 0"} - } -] -} \ No newline at end of file From c64b14e4c835ab4e150c4b8610c6290e59d7b7aa Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 18 Apr 2023 09:31:14 +0200 Subject: [PATCH 410/826] Sync simple-linked-list docs with problem-specifications The simple-linked-list exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 --- .../simple-linked-list/.docs/instructions.md | 20 +++++++++++-------- .../simple-linked-list/.docs/introduction.md | 5 +++++ 2 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 exercises/practice/simple-linked-list/.docs/introduction.md diff --git a/exercises/practice/simple-linked-list/.docs/instructions.md b/exercises/practice/simple-linked-list/.docs/instructions.md index 4d845fac06..04640b1fb0 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.md @@ -1,15 +1,19 @@ # Instructions -Write a simple linked list implementation that uses Elements and a List. +Write a prototype of the music player application. -The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. -They're pervasive in functional programming languages, such as Clojure, Erlang, or Haskell, but far less common in imperative languages such as Ruby or Python. +For the prototype, each song will simply be represented by a number. +Given a range of numbers (the song IDs), create a singly linked list. + +Given a singly linked list, you should be able to reverse the list to play the songs in the opposite order. -The simplest kind of linked list is a singly linked list. -Each element in the list contains data and a "next" field pointing to the next element in the list of elements. +~~~~exercism/note +The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. -This variant of linked lists is often used to represent sequences or push-down stacks (also called a LIFO stack; Last In, First Out). +The simplest kind of linked list is a **singly** linked list. +That means that each element (or "node") contains data, along with something that points to the next node in the list. -As a first take, lets create a singly linked list to contain the range (1..10), and provide functions to reverse a linked list and convert to and from arrays. +If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. -When implementing this in a language with built-in linked lists, implement your own abstract data type. +[intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d +~~~~ diff --git a/exercises/practice/simple-linked-list/.docs/introduction.md b/exercises/practice/simple-linked-list/.docs/introduction.md new file mode 100644 index 0000000000..0e1df72f9b --- /dev/null +++ b/exercises/practice/simple-linked-list/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a music streaming company. + +You've been tasked with creating a playlist feature for your music player application. From 86ac5442fe0e7f6a9dbf5fd4514899cfbb4c76df Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Fri, 7 Apr 2023 15:40:16 -0700 Subject: [PATCH 411/826] list-ops: add foldl canonical test; bring foldr in line with canonical specs and add canonical test --- .../practice/list-ops/.meta/additional_tests.json | 4 ++-- exercises/practice/list-ops/.meta/example.py | 2 +- exercises/practice/list-ops/.meta/template.j2 | 1 - exercises/practice/list-ops/.meta/tests.toml | 2 -- exercises/practice/list-ops/list_ops_test.py | 10 +++++++++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/exercises/practice/list-ops/.meta/additional_tests.json b/exercises/practice/list-ops/.meta/additional_tests.json index 7eb1d65c76..39c0a8bfa6 100644 --- a/exercises/practice/list-ops/.meta/additional_tests.json +++ b/exercises/practice/list-ops/.meta/additional_tests.json @@ -6,7 +6,7 @@ "input": { "list": ["e", "x", "e", "r", "c", "i", "s", "m"], "initial": "!", - "function": "(x, y) -> x + y" + "function": "(acc, el) -> el + acc" }, "expected": "exercism!" }, @@ -19,4 +19,4 @@ "expected": [1, "cat", 4.0, "xyz"] } ] -} \ No newline at end of file +} diff --git a/exercises/practice/list-ops/.meta/example.py b/exercises/practice/list-ops/.meta/example.py index ee43248f99..75f45033e2 100644 --- a/exercises/practice/list-ops/.meta/example.py +++ b/exercises/practice/list-ops/.meta/example.py @@ -29,7 +29,7 @@ def foldr(function, list, initial): if len(list) == 0: return initial else: - return function(list[0], foldr(function, list[1:], initial)) + return function(foldr(function, list[1:], initial), list[0]) def reverse(list): diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index c346d3c20f..cc28280f17 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -2,7 +2,6 @@ {% macro lambdify(function) -%} {% set function = function.replace("(", "", 1).replace(")", "", 1).replace(" ->", ":") %} {% set function = function.replace("modulo", "%") %} - {% set function = function.replace("/", "//") %} lambda {{function}} {%- endmacro %} diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml index 7fe3c8d1a9..08b1edc044 100644 --- a/exercises/practice/list-ops/.meta/tests.toml +++ b/exercises/practice/list-ops/.meta/tests.toml @@ -71,7 +71,6 @@ reimplements = "e56df3eb-9405-416a-b13a-aabb4c3b5194" [d7fcad99-e88e-40e1-a539-4c519681f390] description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" reimplements = "d2cf5644-aee1-4dfc-9b88-06896676fe27" -include = false [aeb576b9-118e-4a57-a451-db49fac20fdc] description = "folds (reduces) the given list from the right with a function -> empty list" @@ -96,7 +95,6 @@ reimplements = "c4b64e58-313e-4c47-9c68-7764964efb8e" [8066003b-f2ff-437e-9103-66e6df474844] description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" reimplements = "be396a53-c074-4db3-8dd6-f7ed003cce7c" -include = false [94231515-050e-4841-943d-d4488ab4ee30] description = "reverse the elements of the list -> empty list" diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index cff6156dc6..a2a301295a 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -63,12 +63,18 @@ def test_foldl_empty_list(self): def test_foldl_direction_independent_function_applied_to_non_empty_list(self): self.assertEqual(foldl(lambda acc, el: el + acc, [1, 2, 3, 4], 5), 15) + def test_foldl_direction_dependent_function_applied_to_non_empty_list(self): + self.assertEqual(foldl(lambda acc, el: el / acc, [1, 2, 3, 4], 24), 64) + def test_foldr_empty_list(self): self.assertEqual(foldr(lambda acc, el: el * acc, [], 2), 2) def test_foldr_direction_independent_function_applied_to_non_empty_list(self): self.assertEqual(foldr(lambda acc, el: el + acc, [1, 2, 3, 4], 5), 15) + def test_foldr_direction_dependent_function_applied_to_non_empty_list(self): + self.assertEqual(foldr(lambda acc, el: el / acc, [1, 2, 3, 4], 24), 9) + def test_reverse_empty_list(self): self.assertEqual(reverse([]), []) @@ -84,7 +90,9 @@ def test_reverse_list_of_lists_is_not_flattened(self): def test_foldr_foldr_add_string(self): self.assertEqual( - foldr(lambda x, y: x + y, ["e", "x", "e", "r", "c", "i", "s", "m"], "!"), + foldr( + lambda acc, el: el + acc, ["e", "x", "e", "r", "c", "i", "s", "m"], "!" + ), "exercism!", ) From 4c5cdbf5c11b4cb3463210de9164469dbaed896b Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Mon, 10 Apr 2023 19:46:21 -0700 Subject: [PATCH 412/826] list-ops tests: swap foldr args order to use func(el, acc) --- exercises/practice/list-ops/.meta/example.py | 2 +- exercises/practice/list-ops/.meta/template.j2 | 10 +++++++++- exercises/practice/list-ops/list_ops_test.py | 8 ++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/exercises/practice/list-ops/.meta/example.py b/exercises/practice/list-ops/.meta/example.py index 75f45033e2..ee43248f99 100644 --- a/exercises/practice/list-ops/.meta/example.py +++ b/exercises/practice/list-ops/.meta/example.py @@ -29,7 +29,7 @@ def foldr(function, list, initial): if len(list) == 0: return initial else: - return function(foldr(function, list[1:], initial), list[0]) + return function(list[0], foldr(function, list[1:], initial)) def reverse(list): diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index cc28280f17..be224ff251 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -1,4 +1,10 @@ {%- import "generator_macros.j2" as macros with context -%} +{% macro swap_args(function) -%} + {% set function = function.replace("(acc, el)", "(el, acc)") %} + {% set function = function.replace("(x, y)", "(y, x)") %} + {{function}} +{%- endmacro %} + {% macro lambdify(function) -%} {% set function = function.replace("(", "", 1).replace(")", "", 1).replace(" ->", ":") %} {% set function = function.replace("modulo", "%") %} @@ -29,8 +35,10 @@ {{ lambdify(input["function"]) }}, {{ input["list"] }} {%- elif case["property"] == "length" or case["property"] == "reverse" -%} {{ input["list"] }} - {%- elif case["property"] == "foldl" or case["property"] == "foldr" -%} + {%- elif case["property"] == "foldl" -%} {{ lambdify(input["function"]) }}, {{ input["list"] }}, {{ stringify(input["initial"]) }} + {%- elif case["property"] == "foldr" -%} + {{ lambdify(swap_args(input["function"])) }}, {{ input["list"] }}, {{ stringify(input["initial"]) }} {%- endif -%} ), {{ stringify(case["expected"]) }} diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index a2a301295a..617be353c6 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -67,13 +67,13 @@ def test_foldl_direction_dependent_function_applied_to_non_empty_list(self): self.assertEqual(foldl(lambda acc, el: el / acc, [1, 2, 3, 4], 24), 64) def test_foldr_empty_list(self): - self.assertEqual(foldr(lambda acc, el: el * acc, [], 2), 2) + self.assertEqual(foldr(lambda el, acc: el * acc, [], 2), 2) def test_foldr_direction_independent_function_applied_to_non_empty_list(self): - self.assertEqual(foldr(lambda acc, el: el + acc, [1, 2, 3, 4], 5), 15) + self.assertEqual(foldr(lambda el, acc: el + acc, [1, 2, 3, 4], 5), 15) def test_foldr_direction_dependent_function_applied_to_non_empty_list(self): - self.assertEqual(foldr(lambda acc, el: el / acc, [1, 2, 3, 4], 24), 9) + self.assertEqual(foldr(lambda el, acc: el / acc, [1, 2, 3, 4], 24), 9) def test_reverse_empty_list(self): self.assertEqual(reverse([]), []) @@ -91,7 +91,7 @@ def test_reverse_list_of_lists_is_not_flattened(self): def test_foldr_foldr_add_string(self): self.assertEqual( foldr( - lambda acc, el: el + acc, ["e", "x", "e", "r", "c", "i", "s", "m"], "!" + lambda el, acc: el + acc, ["e", "x", "e", "r", "c", "i", "s", "m"], "!" ), "exercism!", ) From 7e470c1d54edb7d3746260ddc7512db5ab80e433 Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Wed, 12 Apr 2023 15:15:21 -0700 Subject: [PATCH 413/826] Make foldl also call func(el, acc) --- exercises/practice/list-ops/.meta/example.py | 2 +- exercises/practice/list-ops/.meta/template.j2 | 4 +--- exercises/practice/list-ops/list_ops_test.py | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/exercises/practice/list-ops/.meta/example.py b/exercises/practice/list-ops/.meta/example.py index ee43248f99..eac3b3c774 100644 --- a/exercises/practice/list-ops/.meta/example.py +++ b/exercises/practice/list-ops/.meta/example.py @@ -22,7 +22,7 @@ def foldl(function, list, initial): if len(list) == 0: return initial else: - return foldl(function, list[1:], function(initial, list[0])) + return foldl(function, list[1:], function(list[0], initial)) def foldr(function, list, initial): diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index be224ff251..76c97ace37 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -35,9 +35,7 @@ {{ lambdify(input["function"]) }}, {{ input["list"] }} {%- elif case["property"] == "length" or case["property"] == "reverse" -%} {{ input["list"] }} - {%- elif case["property"] == "foldl" -%} - {{ lambdify(input["function"]) }}, {{ input["list"] }}, {{ stringify(input["initial"]) }} - {%- elif case["property"] == "foldr" -%} + {%- elif case["property"] == "foldl" or case["property"] == "foldr" -%} {{ lambdify(swap_args(input["function"])) }}, {{ input["list"] }}, {{ stringify(input["initial"]) }} {%- endif -%} ), diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index 617be353c6..94dd32e0d5 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -58,13 +58,13 @@ def test_map_non_empty_list(self): self.assertEqual(list_ops_map(lambda x: x + 1, [1, 3, 5, 7]), [2, 4, 6, 8]) def test_foldl_empty_list(self): - self.assertEqual(foldl(lambda acc, el: el * acc, [], 2), 2) + self.assertEqual(foldl(lambda el, acc: el * acc, [], 2), 2) def test_foldl_direction_independent_function_applied_to_non_empty_list(self): - self.assertEqual(foldl(lambda acc, el: el + acc, [1, 2, 3, 4], 5), 15) + self.assertEqual(foldl(lambda el, acc: el + acc, [1, 2, 3, 4], 5), 15) def test_foldl_direction_dependent_function_applied_to_non_empty_list(self): - self.assertEqual(foldl(lambda acc, el: el / acc, [1, 2, 3, 4], 24), 64) + self.assertEqual(foldl(lambda el, acc: el / acc, [1, 2, 3, 4], 24), 64) def test_foldr_empty_list(self): self.assertEqual(foldr(lambda el, acc: el * acc, [], 2), 2) From 648088ed514494594566010f05fcf3aedb9da78f Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Wed, 12 Apr 2023 21:21:25 -0700 Subject: [PATCH 414/826] Add a comment explaining the fold arg swap --- exercises/practice/list-ops/.meta/template.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index 76c97ace37..af52ffbcce 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -1,5 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} {% macro swap_args(function) -%} + {# Args are swapped to use Rust's fold tradition where it calls func(el, acc) and not func(acc, el) #} {% set function = function.replace("(acc, el)", "(el, acc)") %} {% set function = function.replace("(x, y)", "(y, x)") %} {{function}} From 888021b1e8617196338c2cc17cecce323d573609 Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Wed, 12 Apr 2023 21:23:28 -0700 Subject: [PATCH 415/826] Remove the swap and use Haskell tradition, i.e. what the problem spec uses --- exercises/practice/list-ops/.meta/example.py | 4 ++-- exercises/practice/list-ops/.meta/template.j2 | 9 +-------- exercises/practice/list-ops/list_ops_test.py | 14 +++++++------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/exercises/practice/list-ops/.meta/example.py b/exercises/practice/list-ops/.meta/example.py index eac3b3c774..75f45033e2 100644 --- a/exercises/practice/list-ops/.meta/example.py +++ b/exercises/practice/list-ops/.meta/example.py @@ -22,14 +22,14 @@ def foldl(function, list, initial): if len(list) == 0: return initial else: - return foldl(function, list[1:], function(list[0], initial)) + return foldl(function, list[1:], function(initial, list[0])) def foldr(function, list, initial): if len(list) == 0: return initial else: - return function(list[0], foldr(function, list[1:], initial)) + return function(foldr(function, list[1:], initial), list[0]) def reverse(list): diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index af52ffbcce..cc28280f17 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -1,11 +1,4 @@ {%- import "generator_macros.j2" as macros with context -%} -{% macro swap_args(function) -%} - {# Args are swapped to use Rust's fold tradition where it calls func(el, acc) and not func(acc, el) #} - {% set function = function.replace("(acc, el)", "(el, acc)") %} - {% set function = function.replace("(x, y)", "(y, x)") %} - {{function}} -{%- endmacro %} - {% macro lambdify(function) -%} {% set function = function.replace("(", "", 1).replace(")", "", 1).replace(" ->", ":") %} {% set function = function.replace("modulo", "%") %} @@ -37,7 +30,7 @@ {%- elif case["property"] == "length" or case["property"] == "reverse" -%} {{ input["list"] }} {%- elif case["property"] == "foldl" or case["property"] == "foldr" -%} - {{ lambdify(swap_args(input["function"])) }}, {{ input["list"] }}, {{ stringify(input["initial"]) }} + {{ lambdify(input["function"]) }}, {{ input["list"] }}, {{ stringify(input["initial"]) }} {%- endif -%} ), {{ stringify(case["expected"]) }} diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index 94dd32e0d5..a2a301295a 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -58,22 +58,22 @@ def test_map_non_empty_list(self): self.assertEqual(list_ops_map(lambda x: x + 1, [1, 3, 5, 7]), [2, 4, 6, 8]) def test_foldl_empty_list(self): - self.assertEqual(foldl(lambda el, acc: el * acc, [], 2), 2) + self.assertEqual(foldl(lambda acc, el: el * acc, [], 2), 2) def test_foldl_direction_independent_function_applied_to_non_empty_list(self): - self.assertEqual(foldl(lambda el, acc: el + acc, [1, 2, 3, 4], 5), 15) + self.assertEqual(foldl(lambda acc, el: el + acc, [1, 2, 3, 4], 5), 15) def test_foldl_direction_dependent_function_applied_to_non_empty_list(self): - self.assertEqual(foldl(lambda el, acc: el / acc, [1, 2, 3, 4], 24), 64) + self.assertEqual(foldl(lambda acc, el: el / acc, [1, 2, 3, 4], 24), 64) def test_foldr_empty_list(self): - self.assertEqual(foldr(lambda el, acc: el * acc, [], 2), 2) + self.assertEqual(foldr(lambda acc, el: el * acc, [], 2), 2) def test_foldr_direction_independent_function_applied_to_non_empty_list(self): - self.assertEqual(foldr(lambda el, acc: el + acc, [1, 2, 3, 4], 5), 15) + self.assertEqual(foldr(lambda acc, el: el + acc, [1, 2, 3, 4], 5), 15) def test_foldr_direction_dependent_function_applied_to_non_empty_list(self): - self.assertEqual(foldr(lambda el, acc: el / acc, [1, 2, 3, 4], 24), 9) + self.assertEqual(foldr(lambda acc, el: el / acc, [1, 2, 3, 4], 24), 9) def test_reverse_empty_list(self): self.assertEqual(reverse([]), []) @@ -91,7 +91,7 @@ def test_reverse_list_of_lists_is_not_flattened(self): def test_foldr_foldr_add_string(self): self.assertEqual( foldr( - lambda el, acc: el + acc, ["e", "x", "e", "r", "c", "i", "s", "m"], "!" + lambda acc, el: el + acc, ["e", "x", "e", "r", "c", "i", "s", "m"], "!" ), "exercism!", ) From 461e6fc91e418bbd2e36d0504903c3b3ad8cd00d Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Tue, 9 May 2023 13:41:17 -0700 Subject: [PATCH 416/826] [list-ops] Add IsaacG as a contributor --- exercises/practice/list-ops/.meta/config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json index efc025a354..cdb33138a4 100644 --- a/exercises/practice/list-ops/.meta/config.json +++ b/exercises/practice/list-ops/.meta/config.json @@ -8,6 +8,7 @@ "Dog", "dvermd", "gabriel376", + "IsaacG", "N-Parsons", "pheanex", "rootulp", From c1cc9f9594034cc202d16fe0c8d984e23c47e6fa Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 18 May 2023 09:20:22 -0700 Subject: [PATCH 417/826] Updated metadata from problem specifications. --- exercises/practice/binary/.meta/config.json | 2 +- exercises/practice/etl/.meta/config.json | 4 ++-- exercises/practice/hexadecimal/.meta/config.json | 2 +- exercises/practice/nucleotide-count/.meta/config.json | 2 +- exercises/practice/octal/.meta/config.json | 2 +- exercises/practice/point-mutations/.meta/config.json | 2 +- exercises/practice/trinary/.meta/config.json | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/exercises/practice/binary/.meta/config.json b/exercises/practice/binary/.meta/config.json index 7170a4ab19..b9f5b07508 100644 --- a/exercises/practice/binary/.meta/config.json +++ b/exercises/practice/binary/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "All of Computer Science", - "source_url": "http://www.wolframalpha.com/input/?i=binary&a=*C.binary-_*MathWorld-" + "source_url": "https://www.wolframalpha.com/examples/mathematics/numbers/base-conversions" } diff --git a/exercises/practice/etl/.meta/config.json b/exercises/practice/etl/.meta/config.json index 4d99e6314d..32aab8ba3b 100644 --- a/exercises/practice/etl/.meta/config.json +++ b/exercises/practice/etl/.meta/config.json @@ -27,7 +27,7 @@ ".meta/example.py" ] }, - "blurb": "We are going to do the `Transform` step of an Extract-Transform-Load.", - "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "blurb": "Change the data format for scoring a game to more easily add other languages.", + "source": "Based on an exercise by the JumpstartLab team for students at The Turing School of Software and Design.", "source_url": "https://turing.edu" } diff --git a/exercises/practice/hexadecimal/.meta/config.json b/exercises/practice/hexadecimal/.meta/config.json index 352ca16d78..b61e1a2c05 100644 --- a/exercises/practice/hexadecimal/.meta/config.json +++ b/exercises/practice/hexadecimal/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "All of Computer Science", - "source_url": "http://www.wolframalpha.com/examples/NumberBases.html" + "source_url": "https://www.wolframalpha.com/examples/mathematics/numbers/base-conversions" } diff --git a/exercises/practice/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json index 4e91bb7ee9..e0c108f7b8 100644 --- a/exercises/practice/nucleotide-count/.meta/config.json +++ b/exercises/practice/nucleotide-count/.meta/config.json @@ -28,5 +28,5 @@ ] }, "source": "The Calculating DNA Nucleotides_problem at Rosalind", - "source_url": "http://rosalind.info/problems/dna/" + "source_url": "https://rosalind.info/problems/dna/" } diff --git a/exercises/practice/octal/.meta/config.json b/exercises/practice/octal/.meta/config.json index e39e3fb687..4fe6c11bde 100644 --- a/exercises/practice/octal/.meta/config.json +++ b/exercises/practice/octal/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "All of Computer Science", - "source_url": "http://www.wolframalpha.com/input/?i=base+8" + "source_url": "https://www.wolframalpha.com/examples/mathematics/numbers/base-conversions" } diff --git a/exercises/practice/point-mutations/.meta/config.json b/exercises/practice/point-mutations/.meta/config.json index 2d8d765c18..b02f370651 100644 --- a/exercises/practice/point-mutations/.meta/config.json +++ b/exercises/practice/point-mutations/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "The Calculating Point Mutations problem at Rosalind", - "source_url": "http://rosalind.info/problems/hamm/" + "source_url": "https://rosalind.info/problems/hamm/" } diff --git a/exercises/practice/trinary/.meta/config.json b/exercises/practice/trinary/.meta/config.json index 104b505a62..da59ec6c57 100644 --- a/exercises/practice/trinary/.meta/config.json +++ b/exercises/practice/trinary/.meta/config.json @@ -26,5 +26,5 @@ ] }, "source": "All of Computer Science", - "source_url": "http://www.wolframalpha.com/input/?i=binary&a=*C.binary-_*MathWorld-" + "source_url": "https://www.wolframalpha.com/examples/mathematics/numbers/base-conversions" } From e03b3cdd56495a8497bb3108a86213f7a29c43f1 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Wed, 7 Dec 2022 22:58:17 +0100 Subject: [PATCH 418/826] depricated diffie-hellman --- config.json | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/config.json b/config.json index 1d118f79c8..47548a8901 100644 --- a/config.json +++ b/config.json @@ -1247,14 +1247,6 @@ ], "difficulty": 3 }, - { - "slug": "diffie-hellman", - "name": "Diffie-Hellman", - "uuid": "92e2d5f8-7d8a-4e81-a55c-52fa6be80c74", - "practices": ["numbers"], - "prerequisites": ["basics", "bools", "numbers"], - "difficulty": 3 - }, { "slug": "connect", "name": "Connect", @@ -2201,6 +2193,15 @@ "difficulty": 3, "status": "deprecated" }, + { + "slug": "diffie-hellman", + "name": "Diffie-Hellman", + "uuid": "92e2d5f8-7d8a-4e81-a55c-52fa6be80c74", + "practices": [], + "prerequisites": [], + "difficulty": 3, + "status": "deprecated" + }, { "slug": "trinary", "name": "Trinary", From 520c3e637033c1ed1329c46ef880ad03b5582d80 Mon Sep 17 00:00:00 2001 From: Carl Date: Tue, 27 Dec 2022 22:13:00 +0100 Subject: [PATCH 419/826] Started on improvments --- concepts/generators/about.md | 63 +++++++++++++++-------------- concepts/generators/introduction.md | 4 +- concepts/generators/links.json | 4 +- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index f1b97291e4..a136dde507 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -4,65 +4,63 @@ Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. - An example is a function that returns the _squares_ from a given list of numbers. -As currently written, all input must be processed before any values can be returned: - +As currently written, all input must be processed before any values can be returned: ```python >>> def squares(list_of_numbers): ->>> squares = [] ->>> for number in list_of_numbers: ->>> squares.append(number ** 2) ->>> return squares +... squares = [] +... for number in list_of_numbers: +... squares.append(number ** 2) +... return squares ``` You can convert that function into a generator like this: ```python -def squares(list_of_numbers): - for number in list_of_number: - yield number ** 2 +>>> def squares_generator(list_of_numbers): +... for number in list_of_numbers: +... yield number ** 2 ``` The rationale behind this is that you use a generator when you do not need all the values _at once_. This saves memory and processing power, since only the value you are _currently working on_ is calculated. - ## Using a generator -Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. +Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. -To use the `squares()` generator: +To use the `squares_generator()` generator: ```python ->>> squared_numbers = squares([1, 2, 3, 4]) +>>> squared_numbers = squares_generator([1, 2, 3, 4]) >>> for square in squared_numbers: ->>> print(square) +... print(square) +... 1 4 9 16 ``` -Values within a generator can also be produced/accessed via the `next()` function. +Values within a generator can also be produced/accessed via the `next()` function. `next()` calls the `__next__()` method of a generator object, "advancing" or evaluating the generator code up to its `yield` expression, which then "yields" or returns the value. ```python -square_generator = squares([1, 2]) +>>> squared_numbers = squares_generator([1, 2]) ->>> next(square_generator) +>>> next(squared_numbers) 1 ->>> next(square_generator) +>>> next(squared_numbers) 4 ``` When a `generator` is fully consumed and has no more values to return, it throws a `StopIteration` error. ```python ->>> next(square_generator) +>>> next(squared_numbers) Traceback (most recent call last): File "", line 1, in StopIteration @@ -72,12 +70,12 @@ StopIteration Generators are a special sub-set of _iterators_. `Iterators` are the mechanism/protocol that enables looping over _iterables_. -Generators and and the iterators returned by common Python (`iterables`)[https://wiki.python.org/moin/Iterator] act very similarly, but there are some important differences to note: - +Generators and the iterators returned by common Python [`iterables`][iterables] act very similarly, but there are some important differences to note: - Generators are _one-way_; there is no "backing up" to a previous value. - Iterating over generators consume the returned values; no resetting. + - Generators (_being lazily evaluated_) are not sortable and can not be reversed. - Generators do _not_ have `indexes`, so you can't reference a previous or future value using addition or subtraction. @@ -88,7 +86,7 @@ Generators and and the iterators returned by common Python (`iterables`)[https:/ ## The yield expression -The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. +The [yield expression][yield expression] is very similar to the `return` expression. _Unlike_ the `return` expression, `yield` gives up values to the caller at a _specific point_, suspending evaluation/return of any additional values until they are requested. @@ -100,10 +98,10 @@ Note: _Using `yield` expressions is prohibited outside of functions._ ```python >>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 +... current_number = 0 +... while True: +... yield current_number +... current_number += 1 >>> lets_try = infinite_sequence() >>> lets_try.__next__() @@ -123,10 +121,13 @@ Generators are also very helpful when a process or calculation is _complex_, _ex ```python >>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 +... current_number = 0 +... while True: +... yield current_number +... current_number += 1 ``` Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. + +[iterables]: https://wiki.python.org/moin/Iterator +[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index 2c14837133..82a686d1e0 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -1,5 +1,7 @@ # Introduction -A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). +A generator in Python is a _callable function_ that returns a [lazy iterator][lazy iterator]. _Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. + +[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation diff --git a/concepts/generators/links.json b/concepts/generators/links.json index b8ae2f7b64..eea7d4ae33 100644 --- a/concepts/generators/links.json +++ b/concepts/generators/links.json @@ -1,7 +1,7 @@ [ { - "url": "https://docs.python.org/3.8/reference/expressions.html#yield-expressions", - "description": "Official Python 3.8 docs for the yield expression." + "url": "https://docs.python.org/3.11/reference/expressions.html#yield-expressions", + "description": "Official Python 3.11 docs for the yield expression." }, { "url": "https://en.wikipedia.org/wiki/Lazy_evaluation", From e18b1e1c300243713198017aa1b84be53b704f09 Mon Sep 17 00:00:00 2001 From: Carl Date: Tue, 27 Dec 2022 23:28:51 +0100 Subject: [PATCH 420/826] Added an extra exercise --- .../plane-tickets/.docs/instructions.md | 45 +++++++++++++------ .../concept/plane-tickets/.meta/config.json | 2 +- .../concept/plane-tickets/.meta/exemplar.py | 29 +++++++++--- .../concept/plane-tickets/plane_tickets.py | 18 +++++++- .../plane-tickets/plane_tickets_test.py | 36 ++++++++++----- 5 files changed, 98 insertions(+), 32 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 290aad2ebb..9863fe293f 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -2,9 +2,10 @@ Conda airlines is the programming-world's biggest airline, with over 10.000 flights a day! -They are currently assigning all seats to passengers by hand, this will need to automated. +They are currently assigning all seats to passengers by hand, this will need to be automated. -They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. +They have asked _you_ to create software to automate the assigning of seats to passengers. +They require your software to be memory efficient and performant. Conda's airplanes have up to _4 seats_ in each row, and each airplane has many rows. @@ -12,13 +13,29 @@ While the rows are defined using numbers, seats in each row are defined using le You can use this table as a guide: -| x | 1 | 2 | -| :----: | :----: | :----:| -| Row | 5 | 21 | -| Seat letter | A | D | -| Result | 5A | 21D | +| x | 1 | 2 | +| :---------: | :-: | :-: | +| Row | 5 | 21 | +| Seat letter | A | D | +| Result | 5A | 21D | -## 1. Generate an amount of seats +## 1. Generate seat letters + +Implement the `generate_seat_letters()` function that returns an _iterable_ of seat letters given the following variable: + +`amount`: The amount of seat letters to be generated. + +The letters should be generated in alphabetical order, starting with `A` and ending with `D`. + +```python +>>> letters = generate_seat_letters(4) +>>> next(letters) +"A" +>>> next(letters) +"B" +``` + +## 2. Generate an amount of seats Implement the `generate_seats()` function that returns an _iterable_ of seats given the following variable: @@ -37,9 +54,10 @@ _Note: The returned seats should be ordered, like: 1A 1B 1C._ "1B" ``` -## 2. Assign seats to passengers +## 3. Assign seats to passengers -Implement the `assign_seats()` function that returns a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. Given is the following _list_: +Implement the `assign_seats()` function that returns a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. +Given is the following _list_: `passengers`: A list containing passenger names. @@ -50,15 +68,16 @@ Implement the `assign_seats()` function that returns a _dictionary_ of `passenge {'Jerimiah': '1A', 'Eric': '1B', 'Bethaney': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'} ``` -## 3. Ticket codes +## 4. Ticket codes Each ticket has a _12_ character long string code for identification. -This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`. +This code begins with the `assigned_seat` followed by the `flight_id`. +The rest of the code is appended by `0s`. Implement a `generator` that yields a `ticket_number` given the following arguments: -`seat_numbers`: A _list_ of *seat_numbers*. +`seat_numbers`: A _list_ of _seat_numbers_. `flight_id`: A string containing the flight identification. ```python diff --git a/exercises/concept/plane-tickets/.meta/config.json b/exercises/concept/plane-tickets/.meta/config.json index 016ff0cef2..2698188896 100644 --- a/exercises/concept/plane-tickets/.meta/config.json +++ b/exercises/concept/plane-tickets/.meta/config.json @@ -16,6 +16,6 @@ ".meta/exemplar.py" ] }, - "icon": "poker", + "icon": "new-passport", "blurb": "Learn about generators by assigning seats to passengers." } diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index fa8927d759..703e31cb3f 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -1,11 +1,28 @@ -"""Plane Tickets Exercise""" +"""Functions to automate Conda airlines ticketing system.""" import math SEATS_IN_ROW = ['A', 'B', 'C', 'D'] -def generate_seats(amount): +def generate_seat_letters(amount): + """ Generate a series of seat letters for airline boarding. + + :param amount: Amount of seat letters to be generated. (int) + :return: Generator that yields seat letters. + + Seat letters are generated with each row having 4 seats. + These should be sorted from low to high. + + Example: A, B, C, D + + """ + + for seat in range(amount): + yield SEATS_IN_ROW[seat % 4] + + +def generate_seats(amount): """Generate a series of seat numbers for airline boarding. :param amount: Amount of seats to be generated. (int) @@ -21,16 +38,14 @@ def generate_seats(amount): """ amount = amount + 4 if amount >= 13 else amount - + letters = generate_seat_letters(amount) for seat in range(amount): row_number = math.ceil((seat+1) / 4) if row_number != 13: - seat_letter = SEATS_IN_ROW[seat % 4] - yield f'{str(row_number)}{seat_letter}' + yield f'{str(row_number)}{next(letters)}' def assign_seats(passengers): - - """Assign seats to passenger. + """Assign seats to passengers. :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 44d685535b..2d8ad345a7 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -1,4 +1,20 @@ -"""Plane Tickets Exercise""" +"""Functions to automate Conda airlines ticketing system.""" + +def generate_seat_letters(amount): + """ Generate a series of seat letters for airline boarding. + + :param amount: Amount of seat letters to be generated. (int) + :return: Generator that yields seat letters. + + Seat letters are generated with each row having 4 seats. + These should be sorted from low to high. + + Example: A, B, C, D + + """ + + pass + def generate_seats(amount): diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index f4255c43af..1372e5cd2d 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -3,6 +3,7 @@ import pytest from plane_tickets import ( + generate_seat_letters, generate_seats, assign_seats, generate_codes @@ -10,16 +11,31 @@ class PlaneTicketsTest(unittest.TestCase): - @pytest.mark.task(taskno=1) def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. input_var = 5 output_type = Generator error_message = f"Expected: {str(output_type)} type, but got a different type." - self.assertIsInstance(generate_seats(input_var), output_type, msg=error_message) + self.assertIsInstance(generate_seat_letters(input_var), output_type, msg=error_message) @pytest.mark.task(taskno=1) def test_task1_output(self): + input_vars = [1, 2, 3, 4, 5] + output = [["A"], ["A", "B"], ["A", "B", "C"], ["A", "B", "C", "D"], ["A", "B", "C", "D", "A"]] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)." + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(list(generate_seat_letters(input_var)), output, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_task2_is_generator(self): # * Tests if [Task 2] actually returns a generator. + input_var = 5 + output_type = Generator + error_message = f"Expected: {str(output_type)} type, but got a different type." + self.assertIsInstance(generate_seats(input_var), output_type, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_task2_output(self): input_vars = [1, 2, 3, 4, 5] output = [["1A"], ["1A", "1B"], ["1A", "1B", "1C"], ["1A", "1B", "1C", "1D"], ["1A", "1B", "1C", "1D", "2A"]] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): @@ -27,8 +43,8 @@ def test_task1_output(self): with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): self.assertEqual(list(generate_seats(input_var)), output, msg=error_message) - @pytest.mark.task(taskno=1) - def test_task1_skips_row_13(self): + @pytest.mark.task(taskno=2) + def test_task3_skips_row_13(self): input_vars = [14 * 4] output = [["1A", "1B", "1C", "1D", "2A", "2B", "2C", "2D", "3A", "3B", "3C", "3D", "4A", "4B", "4C", "4D", @@ -42,8 +58,8 @@ def test_task1_skips_row_13(self): with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): self.assertEqual(list(generate_seats(input_var)), output, msg=error_message) - @pytest.mark.task(taskno=2) - def test_task2(self): + @pytest.mark.task(taskno=3) + def test_task3(self): input_vars = [["Passenger1", "Passenger2", "Passenger3", "Passenger4", "Passenger5"], ["TicketNo=5644", "TicketNo=2273", "TicketNo=493", "TicketNo=5411", "TicketNo=824"]] output = [{"Passenger1": "1A", "Passenger2": "1B", "Passenger3": "1C", "Passenger4": "1D", "Passenger5": "2A"}, @@ -53,15 +69,15 @@ def test_task2(self): with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): self.assertEqual(assign_seats(input_var), output, msg=error_message) - @pytest.mark.task(taskno=3) - def test_task3_is_generator(self): + @pytest.mark.task(taskno=4) + def test_task4_is_generator(self): input_var = ("11B", "HA80085") output_type = Generator error_message = f"Expected: {str(output_type)} type, but got a different type." self.assertIsInstance(generate_codes(input_var[0], input_var[1]), output_type, msg=error_message) - @pytest.mark.task(taskno=3) - def test_task3(self): + @pytest.mark.task(taskno=4) + def test_task4(self): input_vars = [(["12A", "38B", "69C", "102B"],"KL1022"), (["22C", "88B", "33A", "44B"], "DL1002")] output = [['12AKL1022000', '38BKL1022000', '69CKL1022000', '102BKL102200'], From ea3e886757332b4afb1beeec5017750e89b2f0c6 Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 28 Dec 2022 14:25:37 +0100 Subject: [PATCH 421/826] Small fix --- exercises/concept/plane-tickets/.meta/exemplar.py | 1 - exercises/concept/plane-tickets/plane_tickets.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 703e31cb3f..6bf9ce68a1 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -61,7 +61,6 @@ def assign_seats(passengers): return output def generate_codes(seat_numbers, flight_id): - """Generate codes for a ticket. :param seat_numbers: A list of seat numbers. (list[str]) diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 2d8ad345a7..ff2e8a58ee 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -17,7 +17,6 @@ def generate_seat_letters(amount): def generate_seats(amount): - """ Generate a series of seat numbers for airline boarding. :param amount: Amount of seats to be generated. (int) @@ -35,7 +34,6 @@ def generate_seats(amount): pass def assign_seats(passengers): - """ Assign seats to passengers. :param passengers: A list of strings containing names of passengers. (list[str]) @@ -48,7 +46,6 @@ def assign_seats(passengers): pass def generate_codes(seat_numbers, flight_id): - """Generate codes for a ticket. :param seat_numbers: A list of seat numbers. (list[str]) From 16916968e77938041f2aa849731ffc008af68a1f Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 28 Dec 2022 14:46:58 +0100 Subject: [PATCH 422/826] Runed prettier and major changes to instructions.md --- .../plane-tickets/.docs/instructions.md | 54 ++++++++-------- .../concept/plane-tickets/.meta/design.md | 62 ++++++++----------- 2 files changed, 54 insertions(+), 62 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 9863fe293f..2798bf8651 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -7,25 +7,14 @@ They are currently assigning all seats to passengers by hand, this will need to They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. -Conda's airplanes have up to _4 seats_ in each row, and each airplane has many rows. - -While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being the first _seat_ in the row. - -You can use this table as a guide: - -| x | 1 | 2 | -| :---------: | :-: | :-: | -| Row | 5 | 21 | -| Seat letter | A | D | -| Result | 5A | 21D | - ## 1. Generate seat letters -Implement the `generate_seat_letters()` function that returns an _iterable_ of seat letters given the following variable: +Conda wants to generate seat letters for their airplanes. +Every row has _4 seats_. +They all have the same pattern: `A`, `B`, `C`, `D`. -`amount`: The amount of seat letters to be generated. - -The letters should be generated in alphabetical order, starting with `A` and ending with `D`. +Implement a function `generate_seat_letters()` that accepts an `int` that holds how many seat letters to be generated. +The function should then return an _iterable_ of seat letters. ```python >>> letters = generate_seat_letters(4) @@ -37,14 +26,24 @@ The letters should be generated in alphabetical order, starting with `A` and end ## 2. Generate an amount of seats -Implement the `generate_seats()` function that returns an _iterable_ of seats given the following variable: +Conda wants a system that can generate an amount of seats for their airplanes. +Each airplane has _4 seats_ in each row. +The rows are defined using numbers, starting from `1` and going up. +The seats should be ordered, like: `1A`, `1B`, `1C`, `1D`, `2A`, `2B`, `2C`, `2D`, `3A`, `3B`, `3C`, `3D`, ... + +Here is an example: -`amount`: The amount of seats to be generated. +| x | 1 | 2 | +| :---------: | :-: | :-: | +| Row | 5 | 21 | +| Seat letter | A | D | +| Result | 5A | 21D | Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13. -_Note: The returned seats should be ordered, like: 1A 1B 1C._ +Implement a function `generate_seats()` hat accepts an `int` that accepts an `int` that holds how many seats to be generated. +The function should then return an _iterable_ of seats given. ```python >>> seats = generate_seats(10) @@ -56,10 +55,10 @@ _Note: The returned seats should be ordered, like: 1A 1B 1C._ ## 3. Assign seats to passengers -Implement the `assign_seats()` function that returns a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. -Given is the following _list_: +Now that you have a function that generates seats, you can use it to assign seats to passengers. -`passengers`: A list containing passenger names. +Implement a function `assign_seats()` that accepts a `list` of passenger names. +The function should then return a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. ```python >>> passengers = ['Jerimiah', 'Eric', 'Bethaney', 'Byte', 'SqueekyBoots', 'Bob'] @@ -70,15 +69,16 @@ Given is the following _list_: ## 4. Ticket codes -Each ticket has a _12_ character long string code for identification. +Conda Airlines would like to have a unique code for each ticket. +Since they are a big airline, they have a lot of flights. +Meaning that there are multiple flights with the same seat number. +They want you to create a system that creats a unique ticket that has _12_ characters long string code for identification. This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`. -Implement a `generator` that yields a `ticket_number` given the following arguments: - -`seat_numbers`: A _list_ of _seat_numbers_. -`flight_id`: A string containing the flight identification. +Implement a function `generate_codes()` that accepts a `list` of `seat_numbers` and a `string` with the flight number. +The function should then return a `generator` that yields a `ticket_number`. ```python >>> seat_numbers = ['1A', '17D'] diff --git a/exercises/concept/plane-tickets/.meta/design.md b/exercises/concept/plane-tickets/.meta/design.md index 58542aab87..0323418c85 100644 --- a/exercises/concept/plane-tickets/.meta/design.md +++ b/exercises/concept/plane-tickets/.meta/design.md @@ -2,16 +2,15 @@ This issue describes how to implement the `generators` concept exercise for the ## Goal -The goal of this exercise is to teach the syntax and use of `generators` in Python. +The goal of this exercise is to teach the syntax and use of `generators` in Python. ## Learning objectives -- Understand what generators are and how/when to use them -- Understand how generators relate to `loops` and `iterators` -- Understand how to use the `yield` keyword -- Understand the `__next__()` method -- Create a generator - +- Understand what generators are and how/when to use them +- Understand how generators relate to `loops` and `iterators` +- Understand how to use the `yield` keyword +- Understand the `__next__()` method +- Create a generator ## Out of scope @@ -22,32 +21,28 @@ The goal of this exercise is to teach the syntax and use of `generators` in Pyth - `yield from` - `generators` used as coroutines - ## Concepts covered -- `generators` -- `yield` -- `__next__()` -- `iterators` - +- `generators` +- `yield` +- `__next__()` +- `iterators` ## Prerequisites -- `conditionals` -- `dicts` -- `functions` -- `higher-order-functions` -- `lists` -- `loops` -- `iteration` -- `iterators` -- `sequences` - - +- `conditionals` +- `dicts` +- `functions` +- `higher-order-functions` +- `lists` +- `loops` +- `iteration` +- `iterators` +- `sequences` ## Resources to refer to -- [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators) +- [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators) - [generator (Python official docs glossary)](https://docs.python.org/3/glossary.html#term-generator) - [The yield statement (Python official docs)](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement) - [Yield expressions (Python official docs)](https://docs.python.org/3/reference/expressions.html#yieldexpr) @@ -57,21 +52,20 @@ The goal of this exercise is to teach the syntax and use of `generators` in Pyth ### Hints -- Referring to one or more of the resources linked above, or analogous resources from a trusted source. -- `Generators` section of the Python Docs Functional How to tutorial: [Generators](https://docs.python.org/3/howto/functional.html#generators) - +- Referring to one or more of the resources linked above, or analogous resources from a trusted source. +- `Generators` section of the Python Docs Functional How to tutorial: [Generators](https://docs.python.org/3/howto/functional.html#generators) -## Concept Description +## Concept Description -(_a variant of this can be used for the `v3/languages/python/concepts//about.md` doc and this exercises `introduction.md` doc._) +(_a variant of this can be used for the `v3/languages/python/concepts//about.md` doc and this exercises `introduction.md` doc._) _**Concept Description Needs to Be Filled In Here/Written**_ _Some "extras" that we might want to include as notes in the concept description, or as links in `links.json`:_ -- Additional `Generator-iterator methods`, such as `generator.send()` and `generator.throw()` +- Additional `Generator-iterator methods`, such as `generator.send()` and `generator.throw()` - `generator expressions` -- Asynchronous generator functions +- Asynchronous generator functions - `generators` used as coroutines ## Implementing @@ -80,10 +74,8 @@ The general Python track concept exercise implantation guide can be found [here] Tests should be written using `unittest.TestCase` and the test file named `generators_test.py`. -Code in the `.meta/example.py` file should **only use syntax & concepts introduced in this exercise or one of its prerequisites.** Please do not use comprehensions, generator expressions, or other syntax not previously covered. Please also follow [PEP8](https://www.python.org/dev/peps/pep-0008/) guidelines. +Code in the `.meta/example.py` file should **only use syntax & concepts introduced in this exercise or one of its prerequisites.** Please do not use comprehensions, generator expressions, or other syntax not previously covered. Please also follow [PEP8](https://www.python.org/dev/peps/pep-0008/) guidelines. ## Help If you have any questions while implementing the exercise, please post the questions as comments in this issue, or contact one of the maintainers on our Slack channel. - - From 0a08d61f4d7cabdf8b7885d668223a76fd20f404 Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 28 Dec 2022 21:03:43 +0100 Subject: [PATCH 423/826] spell fixes --- exercises/concept/plane-tickets/.docs/instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 2798bf8651..f49c4ff8df 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -42,7 +42,7 @@ Here is an example: Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13. -Implement a function `generate_seats()` hat accepts an `int` that accepts an `int` that holds how many seats to be generated. +Implement a function `generate_seats()` that accepts an `int` that accepts an `int` that holds how many seats to be generated. The function should then return an _iterable_ of seats given. ```python @@ -72,7 +72,7 @@ The function should then return a _dictionary_ of `passenger` as _key_, and `sea Conda Airlines would like to have a unique code for each ticket. Since they are a big airline, they have a lot of flights. Meaning that there are multiple flights with the same seat number. -They want you to create a system that creats a unique ticket that has _12_ characters long string code for identification. +They want you to create a system that creates a unique ticket that has _12_ characters long string code for identification. This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`. From 4378e0c8d8fdbd7fe7adaa5ac4cae8a93067acef Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 29 Dec 2022 14:18:54 +0100 Subject: [PATCH 424/826] some improvments --- exercises/concept/plane-tickets/.docs/instructions.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index f49c4ff8df..b321addbd3 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -10,8 +10,10 @@ They require your software to be memory efficient and performant. ## 1. Generate seat letters Conda wants to generate seat letters for their airplanes. -Every row has _4 seats_. -They all have the same pattern: `A`, `B`, `C`, `D`. +An airplane is made of rows of seats. +Each row has _4 seats_. +The rows seats has the same naming: `A`, `B`, `C`, `D`. +Meaning the first seat in the row is `A`, the second seat in the row is `B`, and so on. Implement a function `generate_seat_letters()` that accepts an `int` that holds how many seat letters to be generated. The function should then return an _iterable_ of seat letters. @@ -72,7 +74,7 @@ The function should then return a _dictionary_ of `passenger` as _key_, and `sea Conda Airlines would like to have a unique code for each ticket. Since they are a big airline, they have a lot of flights. Meaning that there are multiple flights with the same seat number. -They want you to create a system that creates a unique ticket that has _12_ characters long string code for identification. +They want you to create a system that creates a unique ticket that is _12_ characters long string code for identification. This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`. From 1cc64ac300f3eac61ef5b07a26561709b3d36211 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 29 Dec 2022 14:30:16 +0100 Subject: [PATCH 425/826] Updated doc-string --- .../concept/plane-tickets/.meta/exemplar.py | 18 +++++++++--------- .../concept/plane-tickets/plane_tickets.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 6bf9ce68a1..27995eba7b 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -8,8 +8,8 @@ def generate_seat_letters(amount): """ Generate a series of seat letters for airline boarding. - :param amount: Amount of seat letters to be generated. (int) - :return: Generator that yields seat letters. + :param amount: int - amount of seat letters to be generated. + :return: generator - generator that yields seat letters. Seat letters are generated with each row having 4 seats. These should be sorted from low to high. @@ -25,8 +25,8 @@ def generate_seat_letters(amount): def generate_seats(amount): """Generate a series of seat numbers for airline boarding. - :param amount: Amount of seats to be generated. (int) - :return: Generator that generates seat numbers. (generator) + :param amount: int - Amount of seats to be generated. + :return: generator - generator that yields seat numbers. There should be no row 13 @@ -47,8 +47,8 @@ def generate_seats(amount): def assign_seats(passengers): """Assign seats to passengers. - :param passengers: A list of strings containing names of passengers. (list[str]) - :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. + :param passengers: list[str] - A list of strings containing names of passengers. + :return: dict - with the names of the passengers as keys and seat numbers as values. Example output: {"Foo": "1A", "Bar": "1B"} @@ -63,9 +63,9 @@ def assign_seats(passengers): def generate_codes(seat_numbers, flight_id): """Generate codes for a ticket. - :param seat_numbers: A list of seat numbers. (list[str]) - :param flight_id: A string containing the flight identification. (str) - :return: Generator that generates 12 character long strings. (generator[str]) + :param seat_numbers: list[str] - list of seat numbers. + :param flight_id: str - string containing the flight identification. + :return: generator - generator that yields 12 character long strings. """ diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index ff2e8a58ee..b0c481e46b 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -3,8 +3,8 @@ def generate_seat_letters(amount): """ Generate a series of seat letters for airline boarding. - :param amount: Amount of seat letters to be generated. (int) - :return: Generator that yields seat letters. + :param amount: int - amount of seat letters to be generated. + :return: generator - generator that yields seat letters. Seat letters are generated with each row having 4 seats. These should be sorted from low to high. @@ -19,8 +19,8 @@ def generate_seat_letters(amount): def generate_seats(amount): """ Generate a series of seat numbers for airline boarding. - :param amount: Amount of seats to be generated. (int) - :return: Generator that yields seat numbers. + :param amount: int - Amount of seats to be generated. + :return: generator - generator that yields seat numbers. There should be no row 13 @@ -36,8 +36,8 @@ def generate_seats(amount): def assign_seats(passengers): """ Assign seats to passengers. - :param passengers: A list of strings containing names of passengers. (list[str]) - :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. + :param passengers: list[str] - A list of strings containing names of passengers. + :return: dict - with the names of the passengers as keys and seat numbers as values. Example output: {"Foo": "1A", "Bar": "1B"} @@ -48,8 +48,8 @@ def assign_seats(passengers): def generate_codes(seat_numbers, flight_id): """Generate codes for a ticket. - :param seat_numbers: A list of seat numbers. (list[str]) - :param flight_id: A string containing the flight identification. (str) - :return: Generator that generates 12 character long strings. (generator[str]) + :param seat_numbers: list[str] - list of seat numbers. + :param flight_id: str - string containing the flight identification. + :return: generator - generator that yields 12 character long strings. """ From cd77e99ea0d62a23ac2bea9fc4d8d813681b4c45 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 29 Dec 2022 23:43:26 +0100 Subject: [PATCH 426/826] added ckasses as prereq and minor changes --- concepts/generators/about.md | 4 +++- config.json | 2 +- exercises/concept/plane-tickets/.meta/design.md | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index a136dde507..9a26ab5548 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -94,7 +94,9 @@ When `yield` is evaluated, it pauses the execution of the enclosing function and The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again. -Note: _Using `yield` expressions is prohibited outside of functions._ +```exercism/note +Using `yield` expressions is prohibited outside of functions. +``` ```python >>> def infinite_sequence(): diff --git a/config.json b/config.json index 47548a8901..038123f69e 100644 --- a/config.json +++ b/config.json @@ -184,7 +184,7 @@ "name": "Plane Tickets", "uuid": "3ba3fc89-3e1b-48a5-aff0-5aeaba8c8810", "concepts": ["generators"], - "prerequisites": ["conditionals", "dicts", "lists", "loops"], + "prerequisites": ["conditionals", "dicts", "lists", "loops", "classes"], "status": "wip" }, { diff --git a/exercises/concept/plane-tickets/.meta/design.md b/exercises/concept/plane-tickets/.meta/design.md index 0323418c85..96a4cc3cc9 100644 --- a/exercises/concept/plane-tickets/.meta/design.md +++ b/exercises/concept/plane-tickets/.meta/design.md @@ -39,6 +39,7 @@ The goal of this exercise is to teach the syntax and use of `generators` in Pyth - `iteration` - `iterators` - `sequences` +- `classes` ## Resources to refer to From 9b5bd3910708da4e6bcc5499789a985d1644e08f Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 00:13:33 +0100 Subject: [PATCH 427/826] Updated doc string --- exercises/concept/plane-tickets/.meta/exemplar.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 27995eba7b..20a4f252eb 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -6,13 +6,13 @@ def generate_seat_letters(amount): - """ Generate a series of seat letters for airline boarding. + """Generate a series of seat letters for airline boarding. :param amount: int - amount of seat letters to be generated. :return: generator - generator that yields seat letters. - Seat letters are generated with each row having 4 seats. - These should be sorted from low to high. + Seat letters are generated with from A to D. + After D it should start again with A. Example: A, B, C, D From 0f0d8c86f279969bf0f191d177f2dd330582f513 Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 12:41:11 +0100 Subject: [PATCH 428/826] Updated, links, added blurb, various other changes --- concepts/generators/.meta/config.json | 2 +- concepts/generators/links.json | 4 + .../concept/plane-tickets/.docs/hints.md | 17 ++- .../plane-tickets/.docs/instructions.md | 5 +- .../plane-tickets/.docs/introduction.md | 136 +++++++++++++++++- .../concept/plane-tickets/.meta/exemplar.py | 2 +- .../concept/plane-tickets/plane_tickets.py | 4 +- 7 files changed, 159 insertions(+), 11 deletions(-) diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json index 2204a700df..6c29169d3f 100644 --- a/concepts/generators/.meta/config.json +++ b/concepts/generators/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "Learn about generators by assigning seats to passengers.", + "blurb": "Generator are functions that returns a lazy iterator, lazy iterator is an iterator like: list, tuple, etc. But doesn't need store its content in memory", "authors": ["J08K"], "contributors": [] } diff --git a/concepts/generators/links.json b/concepts/generators/links.json index eea7d4ae33..2f43923cf5 100644 --- a/concepts/generators/links.json +++ b/concepts/generators/links.json @@ -6,5 +6,9 @@ { "url": "https://en.wikipedia.org/wiki/Lazy_evaluation", "description": "Wikipedia page about lazy evaluation" + }, + { + "url": "https://realpython.com/introduction-to-python-generators/", + "description": "Real python, introduction to generators and yield" } ] diff --git a/exercises/concept/plane-tickets/.docs/hints.md b/exercises/concept/plane-tickets/.docs/hints.md index 72d814b22a..11508ee383 100644 --- a/exercises/concept/plane-tickets/.docs/hints.md +++ b/exercises/concept/plane-tickets/.docs/hints.md @@ -1,11 +1,24 @@ # Hints -## 1. Generate an amount of seats +## 1. Generate seat letters + +- The returned value should be of _type_ `generator`. +- You can have a sequence of letters from `A` to `D` and cycle through them. +- And use `yield` to return the next letter. + +## 2. Generate an amount of seats - The returned value should be of _type_ `generator`. - Row `13` should be skipped, so go from `12` to `14`. - Keep in mind that the returned values should be ordered from low to high. `1A, 1B, 2A, ...` +- It might be good to reuse or call other functions you have already completed here. -## 2. Assign seats to passengers +## 3. Assign seats to passengers - Make sure your seat numbers do not have any space in them. +- It might be good to reuse or call other functions you have already completed here. + +## 4. Ticket codes + +- You can use `len()` to get the length of a string. +- You can use `"" * ` to repeat a string. diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index b321addbd3..550c7f3f78 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -14,6 +14,7 @@ An airplane is made of rows of seats. Each row has _4 seats_. The rows seats has the same naming: `A`, `B`, `C`, `D`. Meaning the first seat in the row is `A`, the second seat in the row is `B`, and so on. +After reaching `D` it should start again with `A`. Implement a function `generate_seat_letters()` that accepts an `int` that holds how many seat letters to be generated. The function should then return an _iterable_ of seat letters. @@ -63,10 +64,10 @@ Implement a function `assign_seats()` that accepts a `list` of passenger names. The function should then return a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. ```python ->>> passengers = ['Jerimiah', 'Eric', 'Bethaney', 'Byte', 'SqueekyBoots', 'Bob'] +>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob'] >>> assign_seats(passengers) -{'Jerimiah': '1A', 'Eric': '1B', 'Bethaney': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'} +{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'} ``` ## 4. Ticket codes diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 1b557b447f..5ab9d6d261 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -1,5 +1,135 @@ -# Introduction +# About -A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). +## Constructing a generator -_Lazy iterators_ are similar to iterables such as `lists`, and other types of `iterators` in Python -- but with one key difference: `generators` do not store their `values` in memory, but _generate_ their values as needed or when called. +Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. + +An example is a function that returns the _squares_ from a given list of numbers. +As currently written, all input must be processed before any values can be returned: + +```python +>>> def squares(list_of_numbers): +... squares = [] +... for number in list_of_numbers: +... squares.append(number ** 2) +... return squares +``` + +You can convert that function into a generator like this: + +```python +>>> def squares_generator(list_of_numbers): +... for number in list_of_numbers: +... yield number ** 2 +``` + +The rationale behind this is that you use a generator when you do not need all the values _at once_. + +This saves memory and processing power, since only the value you are _currently working on_ is calculated. + +## Using a generator + +Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. + +To use the `squares_generator()` generator: + +```python +>>> squared_numbers = squares_generator([1, 2, 3, 4]) + +>>> for square in squared_numbers: +... print(square) +... +1 +4 +9 +16 +``` + +Values within a generator can also be produced/accessed via the `next()` function. +`next()` calls the `__next__()` method of a generator object, "advancing" or evaluating the generator code up to its `yield` expression, which then "yields" or returns the value. + +```python +>>> squared_numbers = squares_generator([1, 2]) + +>>> next(squared_numbers) +1 +>>> next(squared_numbers) +4 +``` + +When a `generator` is fully consumed and has no more values to return, it throws a `StopIteration` error. + +```python +>>> next(squared_numbers) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +### Difference between iterables and generators + +Generators are a special sub-set of _iterators_. +`Iterators` are the mechanism/protocol that enables looping over _iterables_. +Generators and the iterators returned by common Python [`iterables`][iterables] act very similarly, but there are some important differences to note: + +- Generators are _one-way_; there is no "backing up" to a previous value. + +- Iterating over generators consume the returned values; no resetting. + +- Generators (_being lazily evaluated_) are not sortable and can not be reversed. + +- Generators do _not_ have `indexes`, so you can't reference a previous or future value using addition or subtraction. + +- Generators cannot be used with the `len()` function. + +- Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator. + +## The yield expression + +The [yield expression][yield expression] is very similar to the `return` expression. + +_Unlike_ the `return` expression, `yield` gives up values to the caller at a _specific point_, suspending evaluation/return of any additional values until they are requested. + +When `yield` is evaluated, it pauses the execution of the enclosing function and returns any values of the function _at that point in time_. + +The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again. + +```exercism/note +Using `yield` expressions is prohibited outside of functions. +``` + +```python +>>> def infinite_sequence(): +... current_number = 0 +... while True: +... yield current_number +... current_number += 1 + +>>> lets_try = infinite_sequence() +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +1 +``` + +## Why generators? + +Generators are useful in a lot of applications. + +When working with a large collection, you might not want to put all of its values into `memory`. +A generator can be used to work on larger data piece-by-piece, saving memory and improving performance. + +Generators are also very helpful when a process or calculation is _complex_, _expensive_, or _infinite_: + +```python +>>> def infinite_sequence(): +... current_number = 0 +... while True: +... yield current_number +... current_number += 1 +``` + +Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. + +[iterables]: https://wiki.python.org/moin/Iterator +[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 20a4f252eb..9a3d702407 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -11,7 +11,7 @@ def generate_seat_letters(amount): :param amount: int - amount of seat letters to be generated. :return: generator - generator that yields seat letters. - Seat letters are generated with from A to D. + Seat letters are generated from A to D. After D it should start again with A. Example: A, B, C, D diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index b0c481e46b..4399791b22 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -6,8 +6,8 @@ def generate_seat_letters(amount): :param amount: int - amount of seat letters to be generated. :return: generator - generator that yields seat letters. - Seat letters are generated with each row having 4 seats. - These should be sorted from low to high. + Seat letters are generated from A to D. + After D it should start again with A. Example: A, B, C, D From bf45569950d3e638b90f005716b014f9126936fc Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 21:08:53 +0100 Subject: [PATCH 429/826] fix --- exercises/concept/plane-tickets/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 550c7f3f78..b22a84f221 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -45,7 +45,7 @@ Here is an example: Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13. -Implement a function `generate_seats()` that accepts an `int` that accepts an `int` that holds how many seats to be generated. +Implement a function `generate_seats()` that accepts an `int` that holds how many seats to be generated. The function should then return an _iterable_ of seats given. ```python From cb75dfa7dfb936f75dbd44bffdc08767ecf42054 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Tue, 10 Jan 2023 23:09:28 +0100 Subject: [PATCH 430/826] Update links.json --- concepts/generators/links.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/generators/links.json b/concepts/generators/links.json index 2f43923cf5..972bbe7ae9 100644 --- a/concepts/generators/links.json +++ b/concepts/generators/links.json @@ -1,7 +1,7 @@ [ { - "url": "https://docs.python.org/3.11/reference/expressions.html#yield-expressions", - "description": "Official Python 3.11 docs for the yield expression." + "url": "https://docs.python.org/3.10/reference/expressions.html#yield-expressions", + "description": "Official Python 3.10 docs for the yield expression." }, { "url": "https://en.wikipedia.org/wiki/Lazy_evaluation", From 89738cea0ccd5820c7fd1fffad98672bae06ba03 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Mon, 5 Jun 2023 10:52:42 +0530 Subject: [PATCH 431/826] Write approaches for Yacht (#3420) * write approaches for yacht * improve wording, structure, and code * update based on bethany's changes * Apply suggestions from code review Co-authored-by: BethanyG * improve snippets, add spm approach * Apply suggestions from code review --------- Co-authored-by: BethanyG --- .../practice/yacht/.approaches/config.json | 28 +++++++ .../yacht/.approaches/functions/content.md | 72 +++++++++++++++++ .../yacht/.approaches/functions/snippet.txt | 8 ++ .../yacht/.approaches/if-structure/content.md | 50 ++++++++++++ .../.approaches/if-structure/snippet.txt | 8 ++ .../yacht/.approaches/introduction.md | 77 +++++++++++++++++++ .../structural-pattern-matching/content.md | 55 +++++++++++++ .../structural-pattern-matching/snippet.txt | 8 ++ 8 files changed, 306 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 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 new file mode 100644 index 0000000000..86d37075b8 --- /dev/null +++ b/exercises/practice/yacht/.approaches/config.json @@ -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"] + } + ] +} diff --git a/exercises/practice/yacht/.approaches/functions/content.md b/exercises/practice/yacht/.approaches/functions/content.md new file mode 100644 index 0000000000..2c6bfe527d --- /dev/null +++ b/exercises/practice/yacht/.approaches/functions/content.md @@ -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/ \ 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..34d270ad89 --- /dev/null +++ b/exercises/practice/yacht/.approaches/functions/snippet.txt @@ -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 +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..581f31d192 --- /dev/null +++ b/exercises/practice/yacht/.approaches/if-structure/content.md @@ -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/ \ 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..fb590cc155 --- /dev/null +++ b/exercises/practice/yacht/.approaches/if-structure/snippet.txt @@ -0,0 +1,8 @@ +ONES = 1 +YACHT = 'YACHT' +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..9717760637 --- /dev/null +++ b/exercises/practice/yacht/.approaches/introduction.md @@ -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 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 c2e991afa42befbd1bf96abb1babbeddd93fa7b9 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Mon, 5 Jun 2023 17:57:00 +0530 Subject: [PATCH 432/826] remove duplicate line --- exercises/practice/yacht/.approaches/introduction.md | 1 - 1 file changed, 1 deletion(-) diff --git a/exercises/practice/yacht/.approaches/introduction.md b/exercises/practice/yacht/.approaches/introduction.md index 9717760637..5a37d16881 100644 --- a/exercises/practice/yacht/.approaches/introduction.md +++ b/exercises/practice/yacht/.approaches/introduction.md @@ -12,7 +12,6 @@ Adhering to the principles of DRY is important - don't repeat yourself if you ca 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 From cc3d9f2b21e739e45c2104a0f9a7e31061e44245 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 29 Dec 2022 21:33:09 +0100 Subject: [PATCH 433/826] Started --- .../binary-octal-hexdecimal/.meta/config.json | 4 + concepts/binary-octal-hexdecimal/about.md | 252 ++++++++++++++++++ .../binary-octal-hexdecimal/introduction.md | 9 + concepts/binary-octal-hexdecimal/links.json | 10 + config.json | 5 + 5 files changed, 280 insertions(+) create mode 100644 concepts/binary-octal-hexdecimal/.meta/config.json create mode 100644 concepts/binary-octal-hexdecimal/about.md create mode 100644 concepts/binary-octal-hexdecimal/introduction.md create mode 100644 concepts/binary-octal-hexdecimal/links.json diff --git a/concepts/binary-octal-hexdecimal/.meta/config.json b/concepts/binary-octal-hexdecimal/.meta/config.json new file mode 100644 index 0000000000..80a508e68b --- /dev/null +++ b/concepts/binary-octal-hexdecimal/.meta/config.json @@ -0,0 +1,4 @@ +{ + "blurb": "Other numerical system in python, binary (0b11), octal (0o71), and hex (0xFF)", + "authors": ["BethanyG", "meatball133"] +} diff --git a/concepts/binary-octal-hexdecimal/about.md b/concepts/binary-octal-hexdecimal/about.md new file mode 100644 index 0000000000..87c168c2cb --- /dev/null +++ b/concepts/binary-octal-hexdecimal/about.md @@ -0,0 +1,252 @@ +# binary, otal, hexdecimal + +Binary, octal, and hexdecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. +Binary is base 2, octal is base 8 and hexdecimal is base 16. +Normal integers are base 10 in python. +Binary, octal, and hexdecimal are all a subset of integers. +Which means that they can only represent whole numbers and support all the operations that we can do with integers. + +## Binary + +[Binary][binary] is a base 2 numeral system. +The most common numeral system is base 10. +So the digits are 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. +Binary is base 2, so the digits are 0 and 1. +It is used to represent the on and off states of a computer. +Binary can create all the numbers that we use in base 10. + +A snipet from the base 2 system looks like this, although it contuines infinitely and doesn't stop at 128: + +| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | +| -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | +| 2 \*\* 7 | 2 \*\* 6 | 2 \*\* 5 | 2 \*\* 4 | 2 \*\* 3 | 2 \*\* 2 | 2 \*\* 1 | 2 \*\* 0 | + +So if we want to represent the number 6, it would in binary be: 110 + +| Place value | 4 | 2 | 1 | +| ------------- | --- | --- | --- | +| Binary number | 1 | 1 | 0 | + +And the operation would be: `4 + 2 + 0 = 6` + +Another example: 19 + +| Place value | 16 | 8 | 4 | 2 | 1 | +| ------------- | --- | --- | --- | --- | --- | +| Binary number | 1 | 0 | 0 | 1 | 1 | + +The binary number would be: 10011 +And the operation would be: `16 + 0 + 0 + 2 + 1 = 19` + +## Binary in Python + +In Python, we can represent binary numbers using the `0b` prefix. +If we write `0b10011`, Python will interpret it as a binary number and convert it to base 10. + +```python +# 0b10011 +>>> 0b10011 +19 + +>>> type(0b10011) + +``` + +Binary in python is a subset of integers, therefore it will act like an integer. + +If you give have a number which is not in the binary system, it will raise a `SyntaxError`. + +```python +Traceback (most recent call last): + File "c:\binary.py", line 1, in + 0b10211 +SyntaxError: invalid digit '2' in binary literal +``` + +### Operations with binary numbers + +Which means that we can do all the operations that we can do with integers. + +```python +# addition +>>> 0b10011 + 0b10011 +38 + +# multiplication +>>> 0b10011 * 0b10011 +361 +``` + +We can do operations with integers and binary numbers. + +```python +# 0b10011 +>>> 0b10011 + 19 +38 +``` + +### Representing binary numbers + +Since python will automaticly convert binary to int, do we have to use the `bin()` function. +If we want to represent a binary number. +`bin()` will return a string with the prefix `0b`. + +```python +# 0b10011 +>>> bin(0b10011) +'0b10011' +``` + +To convert a binary number to an integer, we can use the `int()` function, and pass the string and the base as arguments. + +```python +# 19 +>>> int("0b10011", 2) +``` + +Giving the wrong base will raise a `ValueError`. + +```python +Traceback (most recent call last): + File "c:\Users\carlh\fwfa.py", line 4, in + int("0b10011", 3) +ValueError: invalid literal for int() with base 3: '0b10011' +``` + +### Convering int to binary + +We can also convert an integer to binary using the `bin()` function. + +```python +# 0b10011 +>>> bin(19) +'0b10011' +``` + +### Binary methods + +There are also [methods][numeral-systems] that we can use on binary numbers. + +#### `.bit_length()` + +`.bit_length()` will return the number of bits that are needed to represent the number. +So for example `0b10011` will return 5. + +```python +>>> 0b11011.bit_length() +5 +``` + +#### `.count()` + +```exercism/note +`.count()` requires Python 3.10+. +If you are using the online editor then you don't need to worry about this. +``` + +`.bit_count()` will return the number of ones in the binary number. +So for example `bit_count` will return 3. + +```python +>>> 0b11011.bit_count() +4 +``` + +## Octal + +[Octal][octal] is a base 8 numeral system. +Meaning that the digits are 0, 1, 2, 3, 4, 5, 6, 7. + +In python, we can represent octal numbers using the `0o` prefix. +As with binary, python will automaticly convert octal to int. + +```python +# 0o123 +>>> 0o123 +83 +``` + +As with binary you can do all the operations that you can do with integers and giving a number which is not in the octal system will raise a `SyntaxError`. + +### Representing octal numbers + +To represent an octal number, we can use the `oct()` function. + +```python +# 0o123 +>>> oct(0o123) +'0o123' +``` + +To convert an octal number to an integer, we can use the `int()` function, and pass the string and the base as arguments. + +```python +# 83 +>>> int("0o123", 8) +83 +``` + +As with binary, giving the wrong base will raise a `ValueError`. + +### Convering int to octal + +We can also convert an integer to binary using the `oct()` function. + +```python +# 0o123 +>>> oct(83) +'0o123' +``` + +### hexdecimal + +[Hexdecimal][hexdecimal] is a base 16 numeral system. +Meaning that the digits are 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. +A is 10, B is 11, C is 12, D is 13, E is 14, F is 15. + +In python, we can represent hexdecimal numbers using the `0x` prefix. +As with binary and octal, python will automaticly convert hexdecimal to int. + +```python +# 0x123 +>>> 0x123 +291 +``` + +As with binary and octal you can do all the operations that you can do with integers and giving a number which is not in the hex system will raise a `SyntaxError`. + +### Representing hexdecimal numbers + +To represent an hexdecimal number, we can use the `hex()` function. + +```python +# 0x123 +>>> hex(0x123) +'0x123' +``` + +To convert an hexdecimal number to an integer, we can use the `int()` function, and pass the string and the base as arguments. + +```python +# 291 +>>> int("0x123", 16) +291 +``` + +As with binary and octal, giving the wrong base will raise a `ValueError`. + +### Convering int to hexdecimal + +We can also convert an integer to binary using the `hex()` function. + +```python +# 0x123 +>>> hex(291) +'0x123' +``` + +[binary]: https://en.wikipedia.org/wiki/Binary_number +[octal]: https://en.wikipedia.org/wiki/Octal +[hexdecimal]: https://en.wikipedia.org/wiki/Hexadecimal +[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system +[methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types diff --git a/concepts/binary-octal-hexdecimal/introduction.md b/concepts/binary-octal-hexdecimal/introduction.md new file mode 100644 index 0000000000..0dba132a24 --- /dev/null +++ b/concepts/binary-octal-hexdecimal/introduction.md @@ -0,0 +1,9 @@ +# binary, otal, hexdecimal + +Binary, octal, and hexdecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. +Binary is base 2, octal is base 8 and hexdecimal is base 16. +Normal integers are base 10 in python. +Binary, octal, and hexdecimal are all a subset of integers. +Which means that they can only represent whole numbers and support all the operations that we can do with integers. + +[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system diff --git a/concepts/binary-octal-hexdecimal/links.json b/concepts/binary-octal-hexdecimal/links.json new file mode 100644 index 0000000000..1e7d3acd59 --- /dev/null +++ b/concepts/binary-octal-hexdecimal/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://towardsdatascience.com/binary-hex-and-octal-in-python-20222488cee1", + "description": "Binary, octal, hex in python" + }, + { + "url": "https://en.wikipedia.org/wiki/Numeral_system", + "description": "Numeral system" + } +] \ No newline at end of file diff --git a/config.json b/config.json index 038123f69e..17be12403a 100644 --- a/config.json +++ b/config.json @@ -2235,6 +2235,11 @@ "slug": "binary-data", "name": "Binary Data" }, + { + "uuid": "78dbf248-a1e5-48cb-ba53-def4d92bf2a8", + "slug": "binary-octal-hexdecimal", + "name": "Binary, Octal, and Hexdecimal" + }, { "uuid": "f8e96e60-f746-4c7a-8dc9-df6b77eefdfa", "slug": "bitflags", From b0ce921e5b57bad08b543b1da658dfbec956713f Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 29 Dec 2022 21:34:39 +0100 Subject: [PATCH 434/826] fix --- concepts/binary-octal-hexdecimal/links.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/binary-octal-hexdecimal/links.json b/concepts/binary-octal-hexdecimal/links.json index 1e7d3acd59..8826182cd4 100644 --- a/concepts/binary-octal-hexdecimal/links.json +++ b/concepts/binary-octal-hexdecimal/links.json @@ -7,4 +7,4 @@ "url": "https://en.wikipedia.org/wiki/Numeral_system", "description": "Numeral system" } -] \ No newline at end of file +] From 5632f6e89f5fe53836b8c5f94258f1c12b79f531 Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 12:07:17 +0100 Subject: [PATCH 435/826] Fixes --- concepts/binary-octal-hexdecimal/about.md | 64 ++++++++++------------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/concepts/binary-octal-hexdecimal/about.md b/concepts/binary-octal-hexdecimal/about.md index 87c168c2cb..e3052de9cd 100644 --- a/concepts/binary-octal-hexdecimal/about.md +++ b/concepts/binary-octal-hexdecimal/about.md @@ -1,21 +1,21 @@ -# binary, otal, hexdecimal +# binary, octal, hexadecimal -Binary, octal, and hexdecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. -Binary is base 2, octal is base 8 and hexdecimal is base 16. +Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. +Binary is base 2, octal is base 8 and hexadecimal is base 16. Normal integers are base 10 in python. -Binary, octal, and hexdecimal are all a subset of integers. +Binary, octal, and hexadecimal are all a subset of integers. Which means that they can only represent whole numbers and support all the operations that we can do with integers. ## Binary [Binary][binary] is a base 2 numeral system. The most common numeral system is base 10. -So the digits are 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. +In the base 10 numeral system so are the digits: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Binary is base 2, so the digits are 0 and 1. It is used to represent the on and off states of a computer. Binary can create all the numbers that we use in base 10. -A snipet from the base 2 system looks like this, although it contuines infinitely and doesn't stop at 128: +A snippet from the base 2 system looks like this, although it continues infinitely and doesn't stop at 128: | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | @@ -54,7 +54,7 @@ If we write `0b10011`, Python will interpret it as a binary number and convert i Binary in python is a subset of integers, therefore it will act like an integer. -If you give have a number which is not in the binary system, it will raise a `SyntaxError`. +If you have a number which is not in the binary system, it will raise a `SyntaxError`. ```python Traceback (most recent call last): @@ -65,7 +65,7 @@ SyntaxError: invalid digit '2' in binary literal ### Operations with binary numbers -Which means that we can do all the operations that we can do with integers. +Since binary is a subset of integers, we can do all the operations that we can do with integers. ```python # addition @@ -77,22 +77,20 @@ Which means that we can do all the operations that we can do with integers. 361 ``` -We can do operations with integers and binary numbers. +We can do also have operations with both integers and binary numbers. ```python -# 0b10011 >>> 0b10011 + 19 38 ``` ### Representing binary numbers -Since python will automaticly convert binary to int, do we have to use the `bin()` function. +Since python will automatically convert binary to `int`, do we have to use the `bin()` function. If we want to represent a binary number. -`bin()` will return a string with the prefix `0b`. +`bin()` will return a `string` with the prefix `0b`. ```python -# 0b10011 >>> bin(0b10011) '0b10011' ``` @@ -100,11 +98,11 @@ If we want to represent a binary number. To convert a binary number to an integer, we can use the `int()` function, and pass the string and the base as arguments. ```python -# 19 >>> int("0b10011", 2) +19 ``` -Giving the wrong base will raise a `ValueError`. +Giving the wrong base will raise a `ValueError`: ```python Traceback (most recent call last): @@ -113,7 +111,7 @@ Traceback (most recent call last): ValueError: invalid literal for int() with base 3: '0b10011' ``` -### Convering int to binary +### Converting int to binary We can also convert an integer to binary using the `bin()` function. @@ -144,7 +142,7 @@ So for example `0b10011` will return 5. If you are using the online editor then you don't need to worry about this. ``` -`.bit_count()` will return the number of ones in the binary number. +`.bit_count()` will return the number of **ones** in the binary number. So for example `bit_count` will return 3. ```python @@ -155,10 +153,10 @@ So for example `bit_count` will return 3. ## Octal [Octal][octal] is a base 8 numeral system. -Meaning that the digits are 0, 1, 2, 3, 4, 5, 6, 7. +Meaning that the digits are: 0, 1, 2, 3, 4, 5, 6, 7. In python, we can represent octal numbers using the `0o` prefix. -As with binary, python will automaticly convert octal to int. +As with binary, python will automatically convert octal to int. ```python # 0o123 @@ -173,7 +171,6 @@ As with binary you can do all the operations that you can do with integers and g To represent an octal number, we can use the `oct()` function. ```python -# 0o123 >>> oct(0o123) '0o123' ``` @@ -181,72 +178,67 @@ To represent an octal number, we can use the `oct()` function. To convert an octal number to an integer, we can use the `int()` function, and pass the string and the base as arguments. ```python -# 83 >>> int("0o123", 8) 83 ``` As with binary, giving the wrong base will raise a `ValueError`. -### Convering int to octal +### Converting int to octal We can also convert an integer to binary using the `oct()` function. ```python -# 0o123 >>> oct(83) '0o123' ``` -### hexdecimal +### hexadecimal -[Hexdecimal][hexdecimal] is a base 16 numeral system. +[Hexadecimal][hexadecimal] is a base 16 numeral system. Meaning that the digits are 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. A is 10, B is 11, C is 12, D is 13, E is 14, F is 15. -In python, we can represent hexdecimal numbers using the `0x` prefix. -As with binary and octal, python will automaticly convert hexdecimal to int. +In python, we can represent hexadecimal numbers using the `0x` prefix. +As with binary and octal, python will automatically convert hexadecimal to int. ```python -# 0x123 +# 0o123 >>> 0x123 291 ``` As with binary and octal you can do all the operations that you can do with integers and giving a number which is not in the hex system will raise a `SyntaxError`. -### Representing hexdecimal numbers +### Representing hexadecimal numbers -To represent an hexdecimal number, we can use the `hex()` function. +To represent an hexadecimal number, we can use the `hex()` function. ```python -# 0x123 >>> hex(0x123) '0x123' ``` -To convert an hexdecimal number to an integer, we can use the `int()` function, and pass the string and the base as arguments. +To convert an hexadecimal number to an integer, we can use the `int()` function, and pass the string and the base as arguments. ```python -# 291 >>> int("0x123", 16) 291 ``` As with binary and octal, giving the wrong base will raise a `ValueError`. -### Convering int to hexdecimal +### Converting int to hexadecimal We can also convert an integer to binary using the `hex()` function. ```python -# 0x123 >>> hex(291) '0x123' ``` [binary]: https://en.wikipedia.org/wiki/Binary_number [octal]: https://en.wikipedia.org/wiki/Octal -[hexdecimal]: https://en.wikipedia.org/wiki/Hexadecimal +[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal [numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system [methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types From df09f10f6ada4127530491db47ea5dd7ceaec03c Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 15:46:05 +0100 Subject: [PATCH 436/826] fixes --- .../.meta/config.json | 0 .../about.md | 4 ++-- concepts/binary-octal-hexadecimal/introduction.md | 9 +++++++++ .../links.json | 0 concepts/binary-octal-hexdecimal/introduction.md | 9 --------- config.json | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) rename concepts/{binary-octal-hexdecimal => binary-octal-hexadecimal}/.meta/config.json (100%) rename concepts/{binary-octal-hexdecimal => binary-octal-hexadecimal}/about.md (98%) create mode 100644 concepts/binary-octal-hexadecimal/introduction.md rename concepts/{binary-octal-hexdecimal => binary-octal-hexadecimal}/links.json (100%) delete mode 100644 concepts/binary-octal-hexdecimal/introduction.md diff --git a/concepts/binary-octal-hexdecimal/.meta/config.json b/concepts/binary-octal-hexadecimal/.meta/config.json similarity index 100% rename from concepts/binary-octal-hexdecimal/.meta/config.json rename to concepts/binary-octal-hexadecimal/.meta/config.json diff --git a/concepts/binary-octal-hexdecimal/about.md b/concepts/binary-octal-hexadecimal/about.md similarity index 98% rename from concepts/binary-octal-hexdecimal/about.md rename to concepts/binary-octal-hexadecimal/about.md index e3052de9cd..43ddc905c8 100644 --- a/concepts/binary-octal-hexdecimal/about.md +++ b/concepts/binary-octal-hexadecimal/about.md @@ -1,7 +1,7 @@ # binary, octal, hexadecimal Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. -Binary is base 2, octal is base 8 and hexadecimal is base 16. +Binary is base 2, octal is base 8, and hexadecimal is base 16. Normal integers are base 10 in python. Binary, octal, and hexadecimal are all a subset of integers. Which means that they can only represent whole numbers and support all the operations that we can do with integers. @@ -106,7 +106,7 @@ Giving the wrong base will raise a `ValueError`: ```python Traceback (most recent call last): - File "c:\Users\carlh\fwfa.py", line 4, in + File "c:\binary.py", line 4, in int("0b10011", 3) ValueError: invalid literal for int() with base 3: '0b10011' ``` diff --git a/concepts/binary-octal-hexadecimal/introduction.md b/concepts/binary-octal-hexadecimal/introduction.md new file mode 100644 index 0000000000..7dc63b8dcd --- /dev/null +++ b/concepts/binary-octal-hexadecimal/introduction.md @@ -0,0 +1,9 @@ +# binary, octal, hexadecimal + +Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. +Binary is base 2, octal is base 8, and hexadecimal is base 16. +Normal integers are base 10 in python. +Binary, octal, and hexadecimal are all a subset of integers. +Which means that they can only represent whole numbers and support all the operations that we can do with integers. + +[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system diff --git a/concepts/binary-octal-hexdecimal/links.json b/concepts/binary-octal-hexadecimal/links.json similarity index 100% rename from concepts/binary-octal-hexdecimal/links.json rename to concepts/binary-octal-hexadecimal/links.json diff --git a/concepts/binary-octal-hexdecimal/introduction.md b/concepts/binary-octal-hexdecimal/introduction.md deleted file mode 100644 index 0dba132a24..0000000000 --- a/concepts/binary-octal-hexdecimal/introduction.md +++ /dev/null @@ -1,9 +0,0 @@ -# binary, otal, hexdecimal - -Binary, octal, and hexdecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. -Binary is base 2, octal is base 8 and hexdecimal is base 16. -Normal integers are base 10 in python. -Binary, octal, and hexdecimal are all a subset of integers. -Which means that they can only represent whole numbers and support all the operations that we can do with integers. - -[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system diff --git a/config.json b/config.json index 17be12403a..3e292d7440 100644 --- a/config.json +++ b/config.json @@ -2237,8 +2237,8 @@ }, { "uuid": "78dbf248-a1e5-48cb-ba53-def4d92bf2a8", - "slug": "binary-octal-hexdecimal", - "name": "Binary, Octal, and Hexdecimal" + "slug": "binary-octal-hexadecimal", + "name": "Binary, Octal, and Hexadecimal" }, { "uuid": "f8e96e60-f746-4c7a-8dc9-df6b77eefdfa", From 236eeacad93def73382daff104583f02d8267999 Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 30 Dec 2022 22:20:51 +0100 Subject: [PATCH 437/826] fixes --- concepts/binary-octal-hexadecimal/about.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md index 43ddc905c8..fa6d6a6a92 100644 --- a/concepts/binary-octal-hexadecimal/about.md +++ b/concepts/binary-octal-hexadecimal/about.md @@ -95,7 +95,7 @@ If we want to represent a binary number. '0b10011' ``` -To convert a binary number to an integer, we can use the `int()` function, and pass the string and the base as arguments. +To convert a binary number to an integer, we can use the `int()` function, and pass a string with a binary and the base as arguments. ```python >>> int("0b10011", 2) @@ -175,7 +175,7 @@ To represent an octal number, we can use the `oct()` function. '0o123' ``` -To convert an octal number to an integer, we can use the `int()` function, and pass the string and the base as arguments. +To convert an octal number to an integer, we can use the `int()` function, pass a string with a octal and the base as arguments. ```python >>> int("0o123", 8) @@ -219,7 +219,7 @@ To represent an hexadecimal number, we can use the `hex()` function. '0x123' ``` -To convert an hexadecimal number to an integer, we can use the `int()` function, and pass the string and the base as arguments. +To convert an hexadecimal number to an integer, we can use the `int()` function, pass a string with a hexadecimal and the base as arguments. ```python >>> int("0x123", 16) @@ -238,7 +238,7 @@ We can also convert an integer to binary using the `hex()` function. ``` [binary]: https://en.wikipedia.org/wiki/Binary_number -[octal]: https://en.wikipedia.org/wiki/Octal [hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal -[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system [methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types +[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system +[octal]: https://en.wikipedia.org/wiki/Octal From 86c2a5a4efd844e299104f4055949b05143931e9 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 5 Jun 2023 17:22:00 -0700 Subject: [PATCH 438/826] Apply suggestions from code review @meatball - as you can see, I did a tad bit of violence to this. If you have time/energy, it would be great if you could give it a re-read to make sure that its still OK. _Many thanks!_ --- .../.meta/config.json | 2 +- concepts/binary-octal-hexadecimal/about.md | 148 ++++++++---------- .../binary-octal-hexadecimal/introduction.md | 6 +- 3 files changed, 69 insertions(+), 87 deletions(-) diff --git a/concepts/binary-octal-hexadecimal/.meta/config.json b/concepts/binary-octal-hexadecimal/.meta/config.json index 80a508e68b..6e2a15b607 100644 --- a/concepts/binary-octal-hexadecimal/.meta/config.json +++ b/concepts/binary-octal-hexadecimal/.meta/config.json @@ -1,4 +1,4 @@ { - "blurb": "Other numerical system in python, binary (0b11), octal (0o71), and hex (0xFF)", + "blurb": "Other numerical systems in Python: binary (0b11), octal (0o71), and hex (0xFF)", "authors": ["BethanyG", "meatball133"] } diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md index fa6d6a6a92..1106e43803 100644 --- a/concepts/binary-octal-hexadecimal/about.md +++ b/concepts/binary-octal-hexadecimal/about.md @@ -1,19 +1,16 @@ -# binary, octal, hexadecimal +# Binary, Octal, and Hexadecimal Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. Binary is base 2, octal is base 8, and hexadecimal is base 16. Normal integers are base 10 in python. -Binary, octal, and hexadecimal are all a subset of integers. -Which means that they can only represent whole numbers and support all the operations that we can do with integers. +Binary, octal, and hexadecimal are all representations of integers. +Which means that they represent positive and negative numbers (_including zero_) without fractions or decimals, and support all the operations that we can do with integers. ## Binary -[Binary][binary] is a base 2 numeral system. -The most common numeral system is base 10. -In the base 10 numeral system so are the digits: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. -Binary is base 2, so the digits are 0 and 1. -It is used to represent the on and off states of a computer. -Binary can create all the numbers that we use in base 10. +[Binary][binary] is a base 2 numeral system, using only the digits 0 and 1. +It commonly represents the 0 ("off") and 1 ("on") states of electrical flow through transistors and switches in computers, as well as the positive and negative charges in magnetic storage media. +Binary can represent all the integers that are used in base 10. A snippet from the base 2 system looks like this, although it continues infinitely and doesn't stop at 128: @@ -40,7 +37,7 @@ And the operation would be: `16 + 0 + 0 + 2 + 1 = 19` ## Binary in Python -In Python, we can represent binary numbers using the `0b` prefix. +In Python, we can represent binary literals using the `0b` prefix. If we write `0b10011`, Python will interpret it as a binary number and convert it to base 10. ```python @@ -52,9 +49,9 @@ If we write `0b10011`, Python will interpret it as a binary number and convert i ``` -Binary in python is a subset of integers, therefore it will act like an integer. +Binary in Python is just a different way of writing an integer and so the binary representation **is an integer** for all mathematical operations. -If you have a number which is not in the binary system, it will raise a `SyntaxError`. +If you write a number with a `0b` prefix that is not in the binary system, it will raise a `SyntaxError`. ```python Traceback (most recent call last): @@ -63,9 +60,9 @@ Traceback (most recent call last): SyntaxError: invalid digit '2' in binary literal ``` -### Operations with binary numbers +### Operations with Binary Numbers -Since binary is a subset of integers, we can do all the operations that we can do with integers. +Since binary numbers are integers, we can perform all operations on them that we can with integers. ```python # addition @@ -77,32 +74,38 @@ Since binary is a subset of integers, we can do all the operations that we can d 361 ``` -We can do also have operations with both integers and binary numbers. +We can also perform operations between both binary and integer representations. +However, the usual mathematical operator rules apply: dividing two binary numbers or integer numbers will return a `float`, even if the division does not result in a decimal portion. ```python >>> 0b10011 + 19 38 -``` -### Representing binary numbers +>>> 0b10011/0b10011 +1.0 + +>>> 0b10011/3 +6.333333333333333 + +### Converting to and from Binary Representation -Since python will automatically convert binary to `int`, do we have to use the `bin()` function. -If we want to represent a binary number. -`bin()` will return a `string` with the prefix `0b`. +Python will automatically convert a binary literal into `int`. + To convert an `int` into a binary representation, use the built-in [`bin()`][bin] function. +`bin()` will return a `str` of the binary equivalent with the prefix `0b` . ```python ->>> bin(0b10011) +>>> bin(19) '0b10011' ``` -To convert a binary number to an integer, we can use the `int()` function, and pass a string with a binary and the base as arguments. +To convert a binary literal to an integer, we can use the built-in `int()` function, and pass a string of the binary representation and a base argument: ```python >>> int("0b10011", 2) 19 ``` -Giving the wrong base will raise a `ValueError`: +Giving the wrong base (_or an invalid binary representation_) will raise a `ValueError`: ```python Traceback (most recent call last): @@ -111,52 +114,39 @@ Traceback (most recent call last): ValueError: invalid literal for int() with base 3: '0b10011' ``` -### Converting int to binary - -We can also convert an integer to binary using the `bin()` function. - -```python -# 0b10011 ->>> bin(19) -'0b10011' -``` - -### Binary methods +### Binary Methods -There are also [methods][numeral-systems] that we can use on binary numbers. +There are also some special [methods][numeral-systems] that we can use on binary numbers. -#### `.bit_length()` -`.bit_length()` will return the number of bits that are needed to represent the number. -So for example `0b10011` will return 5. +[`.bit_length()`][bit_length] will return the number of bits that are needed to represent the number: ```python >>> 0b11011.bit_length() 5 ``` -#### `.count()` - -```exercism/note -`.count()` requires Python 3.10+. -If you are using the online editor then you don't need to worry about this. -``` -`.bit_count()` will return the number of **ones** in the binary number. -So for example `bit_count` will return 3. +[`.bit_count()`][bit_count] will return the number of **ones** in the binary number. +For example, `bit_count()` on '0b11011' will return 4: ```python >>> 0b11011.bit_count() 4 -``` + +~~~~exercism/note +If you are working locally, `bit_count()` requires at least Python 3.10. +The Exercism online editor currently supports all features through Python 3.11. +~~~~ + ## Octal [Octal][octal] is a base 8 numeral system. -Meaning that the digits are: 0, 1, 2, 3, 4, 5, 6, 7. +It uses the digits 0, 1, 2, 3, 4, 5, 6, and 7. -In python, we can represent octal numbers using the `0o` prefix. -As with binary, python will automatically convert octal to int. +In Python, we can represent octal numbers using the `0o` prefix. +As with binary, Python automatically converts an octal representation to an `int`. ```python # 0o123 @@ -164,18 +154,20 @@ As with binary, python will automatically convert octal to int. 83 ``` -As with binary you can do all the operations that you can do with integers and giving a number which is not in the octal system will raise a `SyntaxError`. +As with binary, octal numbers **are ints** and support all integer operations. +Prefixing a number with `0o` that is not in the octal system will raise a `SyntaxError`. -### Representing octal numbers + ### Converting to and from Octal Representation + -To represent an octal number, we can use the `oct()` function. +To convert an `int` into an octal representation, you can use the built-in [`oct()`][oct] function. +This acts similarly to the `bin()` function, returning a string: ```python ->>> oct(0o123) +>>> oct(83) '0o123' -``` -To convert an octal number to an integer, we can use the `int()` function, pass a string with a octal and the base as arguments. +To convert an octal number to an integer, we can use the `int()` function, passing an octal string representation and the base (8) as arguments: ```python >>> int("0o123", 8) @@ -184,42 +176,36 @@ To convert an octal number to an integer, we can use the `int()` function, pass As with binary, giving the wrong base will raise a `ValueError`. -### Converting int to octal - -We can also convert an integer to binary using the `oct()` function. - -```python ->>> oct(83) -'0o123' -``` - -### hexadecimal +### Hexadecimal [Hexadecimal][hexadecimal] is a base 16 numeral system. -Meaning that the digits are 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. -A is 10, B is 11, C is 12, D is 13, E is 14, F is 15. +It uses the digits 0 - 9 and the letters A, B, C, D, E, and F. +A is 10, B is 11, C is 12, D is 13, E is 14, and F is 15. -In python, we can represent hexadecimal numbers using the `0x` prefix. -As with binary and octal, python will automatically convert hexadecimal to int. +We can represent hexadecimal numbers in Python using the `0x` prefix. +As with binary and octal, Python will automatically convert hexadecimal literals to `int`. ```python -# 0o123 +# 0x123 >>> 0x123 291 ``` -As with binary and octal you can do all the operations that you can do with integers and giving a number which is not in the hex system will raise a `SyntaxError`. +As with binary and octal - hexidecimal literals **are ints**, and you can perform all integer operations. +Prefixing a non-hexidecimal number with `0x` will raise a `SyntaxError`. ### Representing hexadecimal numbers -To represent an hexadecimal number, we can use the `hex()` function. +### Converting to and from Hexadecimal Representation + +To convert an `int` into a hexadecimal representation, you can use the built-in [`hex()`][hex] function. +This acts similarly to the `bin()` function, returning a string: ```python ->>> hex(0x123) +>>> hex(291) '0x123' -``` -To convert an hexadecimal number to an integer, we can use the `int()` function, pass a string with a hexadecimal and the base as arguments. +To convert a hexadecimal representation to an integer, we can use the `int()` function, passing a hexadecimal string with the base (16) as arguments: ```python >>> int("0x123", 16) @@ -228,16 +214,10 @@ To convert an hexadecimal number to an integer, we can use the `int()` function, As with binary and octal, giving the wrong base will raise a `ValueError`. -### Converting int to hexadecimal - -We can also convert an integer to binary using the `hex()` function. - -```python ->>> hex(291) -'0x123' -``` [binary]: https://en.wikipedia.org/wiki/Binary_number +[bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count +[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length [hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal [methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types [numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system diff --git a/concepts/binary-octal-hexadecimal/introduction.md b/concepts/binary-octal-hexadecimal/introduction.md index 7dc63b8dcd..820aac33ee 100644 --- a/concepts/binary-octal-hexadecimal/introduction.md +++ b/concepts/binary-octal-hexadecimal/introduction.md @@ -3,7 +3,9 @@ Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases. Binary is base 2, octal is base 8, and hexadecimal is base 16. Normal integers are base 10 in python. -Binary, octal, and hexadecimal are all a subset of integers. -Which means that they can only represent whole numbers and support all the operations that we can do with integers. +Binary, octal, and hexadecimal literals are all considered `int` subtypes and Python automatically converts between them. +This means that they can only represent zero, positive, and negative numbers that do not have a fractional or decimal part. +Binary, octal, and hexidecimal numbers support all integer operations. +However, division (_as with ints_) will return a `float`. [numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system From 6967d4a35542a38308b380d0bf9ca6a1b2449ef3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 5 Jun 2023 17:22:29 -0700 Subject: [PATCH 439/826] Update concepts/binary-octal-hexadecimal/about.md --- concepts/binary-octal-hexadecimal/about.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md index 1106e43803..89515abe58 100644 --- a/concepts/binary-octal-hexadecimal/about.md +++ b/concepts/binary-octal-hexadecimal/about.md @@ -218,6 +218,8 @@ As with binary and octal, giving the wrong base will raise a `ValueError`. [binary]: https://en.wikipedia.org/wiki/Binary_number [bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count [bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length +[bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count +[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length [hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal [methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types [numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system From c997cc7f1e57c4b457f936034f07ff8fdfbc5db7 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 5 Jun 2023 17:22:49 -0700 Subject: [PATCH 440/826] Update concepts/binary-octal-hexadecimal/about.md --- concepts/binary-octal-hexadecimal/about.md | 1 - 1 file changed, 1 deletion(-) diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md index 89515abe58..53aa6de104 100644 --- a/concepts/binary-octal-hexadecimal/about.md +++ b/concepts/binary-octal-hexadecimal/about.md @@ -194,7 +194,6 @@ As with binary and octal, Python will automatically convert hexadecimal literals As with binary and octal - hexidecimal literals **are ints**, and you can perform all integer operations. Prefixing a non-hexidecimal number with `0x` will raise a `SyntaxError`. -### Representing hexadecimal numbers ### Converting to and from Hexadecimal Representation From 7aef5e9a8940a624f3cd4d604e99955d610058bf Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 6 Jun 2023 10:29:37 -0700 Subject: [PATCH 441/826] Update concepts/binary-octal-hexadecimal/about.md --- concepts/binary-octal-hexadecimal/about.md | 1 - 1 file changed, 1 deletion(-) diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md index 53aa6de104..667f3eb6cd 100644 --- a/concepts/binary-octal-hexadecimal/about.md +++ b/concepts/binary-octal-hexadecimal/about.md @@ -133,7 +133,6 @@ For example, `bit_count()` on '0b11011' will return 4: ```python >>> 0b11011.bit_count() 4 - ~~~~exercism/note If you are working locally, `bit_count()` requires at least Python 3.10. The Exercism online editor currently supports all features through Python 3.11. From c93e7ad50f0089f086200d4e343abf36b228164d Mon Sep 17 00:00:00 2001 From: Carl Date: Wed, 21 Dec 2022 20:49:38 +0100 Subject: [PATCH 442/826] Start --- concepts/itertools/about.md | 337 +++++++++++++++++++++++++++++++++- concepts/itertools/links.json | 4 +- 2 files changed, 338 insertions(+), 3 deletions(-) diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md index c628150d56..82e6c6161a 100644 --- a/concepts/itertools/about.md +++ b/concepts/itertools/about.md @@ -1,2 +1,337 @@ -#TODO: Add about for this concept. +## Infinite iterators +```exercism/note +To avoid infinite loops, you can use `break` to end a loop. +``` + +### Count() + +`count(start, )` allows to print all values from the start value to infinity. +Count also has an optional step parameter, which allows to print values with a step size other than 1. + +```python +>>> import itertools +>>> for number in itertools.count(5, 2): +... if number > 20: +... break +... else: +... print(number, end=' ') +... +5 7 9 11 13 15 17 19 +``` + +Giving `count()` a negative step size will print values in a descending order. + +```python +>>> import itertools +>>> for number in itertools.count(5, -2): +... if number < -20: +... break +... else: +... print(number, end=' ') +... +5 3 1 -1 -3 -5 -7 -9 -11 -13 -15 -17 -19 +``` + +### Cycle() + +`cycle(iterable)` allows to print all values from the iterable in an infinte loop. +For example a `list`, `tuple`, `string` or `dict` can be used as an iterable. + +```python +>>> import itertools +>>> number = 0 +>>> for letter in itertools.cycle("ABC"): +... if number == 10: +... break +... else: +... print(letter, end=' ') +... number += 1 +... +A B C A B C A B C A +``` + +### Repeat() + +`repeat(object, )` allows to print the same value in an infinte loop. +Although if the optional times parameter is given, the value will be printed that many times. +Meaning that it is not an infinite loop if that parameter is given. + +```python +>>> import itertools +>>> for number in itertools.repeat(5, 3): +... print(number, end=' ') +... +5 5 5 +``` + +## Iterators terminating on the shortest input sequence + +### Accumulate() + +`accumulate(iterable, )` allows to print the accumulated values. + +perhaps skip + +### Chain() + +`chain(iterable1, iterable2...)` creates an irretator of values from the iterables in the order they are given. + +```python +>>> import itertools +>>> for number in itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9]): +... print(number, end=' ') +... +1 2 3 4 5 6 7 8 9 +``` + +Chain can also be used to concate a different amount of iterables to a list or tuple by using the `list()` or `tuple()` function. + +```python +>>> import itertools +>>> list(itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9])) +[1, 2, 3, 4, 5, 6, 7, 8, 9] +``` + +### chain.from_iterable() + +Works like chain but takes a single iterable argument and unpack that iterable into individual iterables. + +```python +>>> import itertools +>>> for number in itertools.chain.from_iterable([[1, 2, 3], [4, 5, 6], [7, 8, 9]]): +... print(number, end=' ') +... +1 2 3 4 5 6 7 8 9 +``` + +### Compress() + +`compress(iterable, selectors)` creates an irretator from the iterable where the corresponding selector is `True`. +The selector can hold bools but can also hold integers, where 0 is `False` and any other integer is `True`. + +```python +>>> import itertools +>>> for letter in itertools.compress("Exercism", [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]): +... print(letter, end=' ') +... +E r c s m +``` + +### Dropwhile() + +skip + +### Filterfalse() + +skip + +### Groupby() + +probably skip + +### Islice() + +`islice(iterable, , , )` creates an irretator of the values from the iterable, from the start index to the stop index with a step size of step. + +```python +>>> import itertools +>>> for letter in itertools.islice("Exercism", 2, 5, 2): +... print(letter, end=' ') +... +e c +``` + +### Pairwise() + +```exercism/note +`Pairwise()` requires Python 3.10+. +If you are using the online editor then you don't need to worry about this. +``` + +`Pairwise(iterable)` was intruduced in Python 3.10 and returns an iterator of overlapping pairs of values from the input iterable. + +```python +>>> import itertools +>>> for pair in itertools.pairwise("Exercism"): +... print(pair, end=' ') +... +('E', 'x') ('x', 'e') ('e', 'r') ('r', 'c') ('c', 'i') ('i', 's') ('s', 'm') +``` + +### Starmap() + +Pehaps skip + +### Takewhile() + +skip + +### Tee() + +Talk with Bethany about + +### Zip_longest() + +#### Explaning zip + +```exercism/caution +Zip in computer science(programming) is not the same as zip in computer terms. +Zip in computer terms reffers to a file format that supports compression. +While zip in computer science(programming) reffers to the `zip()` function. +``` + +`zip()` is a built in function and is not apart of the `itertools` module. +It takes any number of iterables and returns an iterator of tuples. +Where the i-th tuple contains the i-th element from each of the argument iterables. +Meaning the first tuple will contain the first element from each iterable, the second tuple will contain the second element from each iterable and so on. + +```python +>>> zipped = zip(['x', 'y', 'z'], [1, 2, 3], [True, False, True]) +>>> list(zipped) +[('x', 1, True),('y', 2, False), ('z', 3, True)] +``` + +If the iterables are not the same length, then the iterator will stop when the shortest iterable is exhausted. + +```python +>>> zipped = zip(['x', 'y', 'z'], [1, 2, 3, 4], [True, False]) +>>> list(zipped) +[('x', 1, True),('y', 2, False)] +``` + +#### Explaning zip_longest + +`zip_longest(iterator, )` is a function from the `itertools` module. +The difference between `zip_longest()` and `zip()` is that `zip_longest()` will continue until the longest iterable is exhausted. +If the iterables are not the same length the `fillvalue` will be used to fill in the missing values. +By the default the `fillvalue` is `None`. + +```python +>>> import itertools +>>> zipped = itertools.zip_longest(['x', 'y', 'z'], [1, 2, 3, 4], [True, False]) +>>> list(zipped) +[('x', 1, True),('y', 2, False), ('z', 3, None), (None, 4, None)] +``` + +An example where a fillvalue is given: + +```python +>>> import itertools +>>> zipped = itertools.zip_longest(['x', 'y', 'z'], [1, 2, 3, 4], [True, False], fillvalue='fill') +>>> list(zipped) +[('x', 1, True),('y', 2, False), ('z', 3, 'fill'), ('fill', 4, 'fill')] +``` + +## Combinatoric iterators + +### Product() + +`product(iterable1, iterable2..., )` creates an iterator of tuples where the i-th tuple contains the i-th element from each of the argument iterables. +The repeat keyword argument can be used to specify the number of times the input iterables are repeated. +By default the repeat keyword argument is 1. + +```python +>>> import itertools +>>> for product in itertools.product("ABCD", repeat=1): +... print(product, end=' ') +... +('A',) ('B',) ('C',) ('D',) +``` + +```python +>>> import itertools +>>> for product in itertools.product("ABCD", repeat=2): +... print(product, end=' ') +... +('A', 'A') ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'A') ('B', 'B') ('B', 'C') ('B', 'D') ('C', 'A') ('C', 'B') ('C', 'C') ('C', 'D') ('D', 'A') ('D', 'B') ('D', 'C') ('D', 'D') +``` + +The last one here can be seen as doing a nested for loop. +When you increase the repeat value the number of iterations increases exponentially. +The example above is a n\*\*2 iteration. + +```python +>>> import itertools +>>> for letter1 in "ABCD": +... for letter2 in "ABCD": +... print((letter1, letter2), end=' ') +... +('A', 'A') ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'A') ('B', 'B') ('B', 'C') ('B', 'D') ('C', 'A') ('C', 'B') ('C', 'C') ('C', 'D') ('D', 'A') ('D', 'B') ('D', 'C') ('D', 'D') +``` + +You can also give it multiple iterables. + +```python +>>> import itertools +>>> for product in itertools.product("ABCD", "xy" repeat=1): +... print(product, end=' ') +... +('A', 'x') ('A', 'y') ('B', 'x') ('B', 'y') ('C', 'x') ('C', 'y') ('D', 'x') ('D', 'y') +``` + +Here is an example of doing it wihout `product()`. +It looks similliar to the last example but since we have two iterables we need to nest the for loops. +Even though the proudct is given repeat=1. +The reasson to why it is only 2 for loops earlier was because we only had one iterable. +If we had two iterables and gave it repeat=2 we would need 4 for loops. +Since 2 \* 2 = 4. + +```python +>>> for letter1 in "ABCD": +... for letter2 in "xy": +... print((letter1, letter2), end=' ') +... +('A', 'x') ('A', 'y') ('B', 'x') ('B', 'y') ('C', 'x') ('C', 'y') ('D', 'x') ('D', 'y') +``` + +### Permutations() + +`permutations(iterable, )` creates an iterator of tuples. +It works like `product()` but it doesnt repeat values from a specific positoon from the iterable and can only take one iterable. +The "r" keyword argument can be used to specify the number of times the input iterables are repeated. +By default the "r" keyword argument is None. +If "r" is None then the length of the iterable is used. + +```python +>>> import itertools +>>> for permutation in itertools.permutations("ABCD", repeat=1): +... print(permutation, end=' ') +... +('A',) ('B',) ('C',) ('D',) +``` + +```python +>>> import itertools +>>> for permutation in itertools.permutations("ABCD", repeat=2): +... print(permutation, end=' ') +... +('A', 'B') ('A', 'C') ('A', 'D') ('B', 'A') ('B', 'C') ('B', 'D') ('C', 'A') ('C', 'B') ('C', 'D') ('D', 'A') ('D', 'B') ('D', 'C') +``` + +### Combinations() + +`combinations(iterable, r)` finds all the possible combinations of the given iterable. +The r keyword argument is used to specify the length of the tuples generated. + +```python +>>> import itertools +>>> for combination in itertools.combinations("ABCD", 2): +... print(combination, end=' ') +... +('A', 'B') ('A', 'C') ('A', 'D') ('B', 'C') ('B', 'D') ('C', 'D') +``` + +### Combinations_with_replacement() + +`combinations_with_replacement(iterable, r)` finds all the possible combinations of the given iterable. +The r keyword argument is used to specify the length of the tuples generated. +The difference between this and `combinations()` is that it can repeat values. + +```python +>>> import itertools +>>> for combination in itertools.combinations_with_replacement("ABCD", 2): +... print(combination, end=' ') +... +('A', 'A') ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'B') ('B', 'C') ('B', 'D') ('C', 'C') ('C', 'D') ('D', 'D') +``` diff --git a/concepts/itertools/links.json b/concepts/itertools/links.json index eb5fb7c38a..8a44906b3f 100644 --- a/concepts/itertools/links.json +++ b/concepts/itertools/links.json @@ -1,7 +1,7 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/itertools.htm", + "description": "Offical Python documentation for the itertools module." }, { "url": "http://example.com/", From 43e5f990346c8131ac234690d9e7e3100fa6a04c Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 22 Dec 2022 16:05:04 +0100 Subject: [PATCH 443/826] Added the changes we talked about and some extras --- concepts/itertools/.meta/config.json | 2 +- concepts/itertools/about.md | 195 +++++++++++++-------------- 2 files changed, 93 insertions(+), 104 deletions(-) diff --git a/concepts/itertools/.meta/config.json b/concepts/itertools/.meta/config.json index 9b9e8da5a9..3bf2b514cc 100644 --- a/concepts/itertools/.meta/config.json +++ b/concepts/itertools/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "authors": ["bethanyg", "meatball133"], "contributors": [] } diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md index 82e6c6161a..e23860db98 100644 --- a/concepts/itertools/about.md +++ b/concepts/itertools/about.md @@ -1,78 +1,5 @@ -## Infinite iterators - -```exercism/note -To avoid infinite loops, you can use `break` to end a loop. -``` - -### Count() - -`count(start, )` allows to print all values from the start value to infinity. -Count also has an optional step parameter, which allows to print values with a step size other than 1. - -```python ->>> import itertools ->>> for number in itertools.count(5, 2): -... if number > 20: -... break -... else: -... print(number, end=' ') -... -5 7 9 11 13 15 17 19 -``` - -Giving `count()` a negative step size will print values in a descending order. - -```python ->>> import itertools ->>> for number in itertools.count(5, -2): -... if number < -20: -... break -... else: -... print(number, end=' ') -... -5 3 1 -1 -3 -5 -7 -9 -11 -13 -15 -17 -19 -``` - -### Cycle() - -`cycle(iterable)` allows to print all values from the iterable in an infinte loop. -For example a `list`, `tuple`, `string` or `dict` can be used as an iterable. - -```python ->>> import itertools ->>> number = 0 ->>> for letter in itertools.cycle("ABC"): -... if number == 10: -... break -... else: -... print(letter, end=' ') -... number += 1 -... -A B C A B C A B C A -``` - -### Repeat() - -`repeat(object, )` allows to print the same value in an infinte loop. -Although if the optional times parameter is given, the value will be printed that many times. -Meaning that it is not an infinite loop if that parameter is given. - -```python ->>> import itertools ->>> for number in itertools.repeat(5, 3): -... print(number, end=' ') -... -5 5 5 -``` - ## Iterators terminating on the shortest input sequence -### Accumulate() - -`accumulate(iterable, )` allows to print the accumulated values. - -perhaps skip - ### Chain() `chain(iterable1, iterable2...)` creates an irretator of values from the iterables in the order they are given. @@ -95,11 +22,16 @@ Chain can also be used to concate a different amount of iterables to a list or t ### chain.from_iterable() -Works like chain but takes a single iterable argument and unpack that iterable into individual iterables. +Works like chain but takes a single nested iterable, unpacking that iterable into individual iterables. ```python >>> import itertools ->>> for number in itertools.chain.from_iterable([[1, 2, 3], [4, 5, 6], [7, 8, 9]]): +>>> for number in itertools.chain.from_iterable( + [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ]): ... print(number, end=' ') ... 1 2 3 4 5 6 7 8 9 @@ -107,32 +39,30 @@ Works like chain but takes a single iterable argument and unpack that iterable i ### Compress() -`compress(iterable, selectors)` creates an irretator from the iterable where the corresponding selector is `True`. -The selector can hold bools but can also hold integers, where 0 is `False` and any other integer is `True`. +`compress(iterable, selectors)` creates an iterator from the input iterable where the corresponding selector is `True`. +The selector can be `True`/`False` or integers, where 0 is `False` and 1 is `True`. ```python >>> import itertools ->>> for letter in itertools.compress("Exercism", [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]): +>>> for letter in itertools.compress("Exercism", [1, 0, 0, 1, 1, 0, 1, 1]): ... print(letter, end=' ') ... E r c s m ``` -### Dropwhile() - -skip - -### Filterfalse() +With `True`/`False`: -skip - -### Groupby() - -probably skip +```python +>>> import itertools +>>> for letter in itertools.compress("Exercism", [True, False, False, True, True, False, True, True]): +... print(letter, end=' ') +... +E r c s m +``` ### Islice() -`islice(iterable, , , )` creates an irretator of the values from the iterable, from the start index to the stop index with a step size of step. +`islice(iterable, start, , )` creates a new iterator from the slice (from the start index to the stop index with a step size of step). ```python >>> import itertools @@ -159,14 +89,6 @@ If you are using the online editor then you don't need to worry about this. ('E', 'x') ('x', 'e') ('e', 'r') ('r', 'c') ('c', 'i') ('i', 's') ('s', 'm') ``` -### Starmap() - -Pehaps skip - -### Takewhile() - -skip - ### Tee() Talk with Bethany about @@ -176,15 +98,13 @@ Talk with Bethany about #### Explaning zip ```exercism/caution -Zip in computer science(programming) is not the same as zip in computer terms. -Zip in computer terms reffers to a file format that supports compression. -While zip in computer science(programming) reffers to the `zip()` function. +Pythons `zip()` function should not be confused with the zip compression format. ``` `zip()` is a built in function and is not apart of the `itertools` module. It takes any number of iterables and returns an iterator of tuples. Where the i-th tuple contains the i-th element from each of the argument iterables. -Meaning the first tuple will contain the first element from each iterable, the second tuple will contain the second element from each iterable and so on. +For example, the first tuple will contain the first element from each iterable, the second tuple will contain the second element from each iterable, and so on until the shortest iterable is exhausted. ```python >>> zipped = zip(['x', 'y', 'z'], [1, 2, 3], [True, False, True]) @@ -203,8 +123,8 @@ If the iterables are not the same length, then the iterator will stop when the s #### Explaning zip_longest `zip_longest(iterator, )` is a function from the `itertools` module. -The difference between `zip_longest()` and `zip()` is that `zip_longest()` will continue until the longest iterable is exhausted. -If the iterables are not the same length the `fillvalue` will be used to fill in the missing values. +Unlink `zip()`, it will not stop when the shortest iterable is exhausted. +If the iterables are not the same length, `fillvalue` will be used to pad missing values. By the default the `fillvalue` is `None`. ```python @@ -239,6 +159,8 @@ By default the repeat keyword argument is 1. ('A',) ('B',) ('C',) ('D',) ``` +Giving a repeat value of 2: + ```python >>> import itertools >>> for product in itertools.product("ABCD", repeat=2): @@ -335,3 +257,70 @@ The difference between this and `combinations()` is that it can repeat values. ... ('A', 'A') ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'B') ('B', 'C') ('B', 'D') ('C', 'C') ('C', 'D') ('D', 'D') ``` + +## Infinite iterators + +```exercism/note +To avoid infinite loops, you can use `break` to end a loop. +``` + +### Count() + +`count(start, )` produces all values from the start value to infinity. +Count also has an optional step parameter, which will produce values with a step size other than 1. + +```python +>>> import itertools +>>> for number in itertools.count(5, 2): +... if number > 20: +... break +... else: +... print(number, end=' ') +... +5 7 9 11 13 15 17 19 +``` + +Giving `count()` a negative step size will produces values in a descending order. + +```python +>>> import itertools +>>> for number in itertools.count(5, -2): +... if number < -20: +... break +... else: +... print(number, end=' ') +... +5 3 1 -1 -3 -5 -7 -9 -11 -13 -15 -17 -19 +``` + +### Cycle() + +`cycle(iterable)` produces all values from the iterable in an infinte loop. +A `list`, `tuple`, `string`, `dict` or any other iterable can be used. + +```python +>>> import itertools +>>> number = 0 +>>> for letter in itertools.cycle("ABC"): +... if number == 10: +... break +... else: +... print(letter, end=' ') +... number += 1 +... +A B C A B C A B C A +``` + +### Repeat() + +`repeat(object, )` produces the same value in an infinte loop. +Although if the optional times parameter is given, the value will produces that many times. +Meaning that it is not an infinite loop if that parameter is given. + +```python +>>> import itertools +>>> for number in itertools.repeat(5, 3): +... print(number, end=' ') +... +5 5 5 +``` From 99689d80c9afa1dbd7a54e4681c8f41ea56bc011 Mon Sep 17 00:00:00 2001 From: Carl Date: Fri, 23 Dec 2022 17:01:41 +0100 Subject: [PATCH 444/826] Added extra parts --- concepts/itertools/about.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md index e23860db98..4cfe5bca53 100644 --- a/concepts/itertools/about.md +++ b/concepts/itertools/about.md @@ -14,12 +14,22 @@ Chain can also be used to concate a different amount of iterables to a list or tuple by using the `list()` or `tuple()` function. +Using `list()`: + ```python >>> import itertools >>> list(itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9])) [1, 2, 3, 4, 5, 6, 7, 8, 9] ``` +Using `tuple()`: + +```python +>>> import itertools +>>> tuple(itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9])) +(1, 2, 3, 4, 5, 6, 7, 8, 9) +``` + ### chain.from_iterable() Works like chain but takes a single nested iterable, unpacking that iterable into individual iterables. From 85f23648a3540530f3ec10bc51923b25f6975baf Mon Sep 17 00:00:00 2001 From: Carl Date: Sat, 24 Dec 2022 23:34:24 +0100 Subject: [PATCH 445/826] Added blurb --- concepts/itertools/about.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md index 4cfe5bca53..366515cf1a 100644 --- a/concepts/itertools/about.md +++ b/concepts/itertools/about.md @@ -270,6 +270,10 @@ The difference between this and `combinations()` is that it can repeat values. ## Infinite iterators +Most of iterator from the `itertools` module get exhausted after a time. +But there are some that are infinite, these are known as infinte iterators. +These iterators will will keep producing values until you tell them to stop. + ```exercism/note To avoid infinite loops, you can use `break` to end a loop. ``` From 5d31c1b37d09387016f0860cb879b42e4db5b4a3 Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 13:36:11 +0100 Subject: [PATCH 446/826] Started on resistor_color_master --- config.json | 15 ++ .../.docs/instructions.md | 54 +++++++ .../resistor-color-master/.meta/config.json | 20 +++ .../resistor-color-master/.meta/example.py | 47 ++++++ .../resistor-color-master/.meta/hi.json | 147 ++++++++++++++++++ .../resistor-color-master/.meta/template.j2 | 15 ++ .../resistor-color-master/.meta/tests.toml | 40 +++++ .../resistor_color_master.py | 49 ++++++ .../resistor_color_master_test.py | 51 ++++++ 9 files changed, 438 insertions(+) create mode 100644 exercises/practice/resistor-color-master/.docs/instructions.md create mode 100644 exercises/practice/resistor-color-master/.meta/config.json create mode 100644 exercises/practice/resistor-color-master/.meta/example.py create mode 100644 exercises/practice/resistor-color-master/.meta/hi.json create mode 100644 exercises/practice/resistor-color-master/.meta/template.j2 create mode 100644 exercises/practice/resistor-color-master/.meta/tests.toml create mode 100644 exercises/practice/resistor-color-master/resistor_color_master.py create mode 100644 exercises/practice/resistor-color-master/resistor_color_master_test.py diff --git a/config.json b/config.json index 3e292d7440..84c5f1ea6f 100644 --- a/config.json +++ b/config.json @@ -970,6 +970,21 @@ ], "difficulty": 3 }, + { + "slug": "resistor-color-master", + "name": "Resistor Color Master", + "uuid": "f987a5b7-96f2-49c2-99e8-aef30d23dd58", + "practices": ["list-methods"], + "prerequisites": [ + "basics", + "bools", + "lists", + "numbers", + "strings", + "comparisons" + ], + "difficulty": 3 + }, { "slug": "matrix", "name": "Matrix", diff --git a/exercises/practice/resistor-color-master/.docs/instructions.md b/exercises/practice/resistor-color-master/.docs/instructions.md new file mode 100644 index 0000000000..fcc76958a5 --- /dev/null +++ b/exercises/practice/resistor-color-master/.docs/instructions.md @@ -0,0 +1,54 @@ +# Instructions + +If you want to build something using a Raspberry Pi, you'll probably use _resistors_. +For this exercise, you need to know only three things about them: + +- Each resistor has a resistance value. +- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. + To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. +- Each band acts as a digit of a number. + For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. + In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. + The program will take 3 colors as input, and outputs the correct value, in ohms. + The color bands are encoded as follows: + +- Black: 0 +- Brown: 1 +- Red: 2 +- Orange: 3 +- Yellow: 4 +- Green: 5 +- Blue: 6 +- Violet: 7 +- Grey: 8 +- White: 9 + +In `resistor-color duo` you decoded the first two colors. +For instance: orange-orange got the main value `33`. +The third color stands for how many zeros need to be added to the main value. +The main value plus the zeros gives us a value in ohms. +For the exercise it doesn't matter what ohms really are. +For example: + +- orange-orange-black would be 33 and no zeros, which becomes 33 ohms. +- orange-orange-red would be 33 and 2 zeros, which becomes 3300 ohms. +- orange-orange-orange would be 33 and 3 zeros, which becomes 33000 ohms. + +(If Math is your thing, you may want to think of the zeros as exponents of 10. +If Math is not your thing, go with the zeros. +It really is the same thing, just in plain English instead of Math lingo.) + +This exercise is about translating the colors into a label: + +> "... ohms" + +So an input of `"orange", "orange", "black"` should return: + +> "33 ohms" + +When we get more than a thousand ohms, we say "kiloohms". +That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. + +So an input of `"orange", "orange", "orange"` should return: + +> "33 kiloohms" diff --git a/exercises/practice/resistor-color-master/.meta/config.json b/exercises/practice/resistor-color-master/.meta/config.json new file mode 100644 index 0000000000..23a09a2cdd --- /dev/null +++ b/exercises/practice/resistor-color-master/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "meatball133", + "bethanyg" + ], + "files": { + "solution": [ + "resistor_color_master.py" + ], + "test": [ + "resistor_color_master_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Convert color codes, as used on resistors, to a human-readable label.", + "source": "Maud de Vries, Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/issues/1549" +} diff --git a/exercises/practice/resistor-color-master/.meta/example.py b/exercises/practice/resistor-color-master/.meta/example.py new file mode 100644 index 0000000000..a7dd1cd367 --- /dev/null +++ b/exercises/practice/resistor-color-master/.meta/example.py @@ -0,0 +1,47 @@ +COLORS = [ + 'black', + 'brown', + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'violet', + 'grey', + 'white' +] + +COLORS_TOLERANCE = { + 'brown': 1, + 'red': 2, + 'green': 0.5, + 'blue': 0.25, + 'violet': 0.1, + 'grey': 0.05, + 'gold': 5, + 'silver': 10 +} + + +def label(colors): + if len(colors) == 1: + return f'0 ohms' + elif len(colors) == 4: + value = 10 * COLORS.index(colors[0]) + COLORS.index(colors[1]) + value *= 10 ** COLORS.index(colors[2]) + value, unit = color_code(value) + return f'{value} {unit}, {COLORS_TOLERANCE[colors[3]]}%' + else: + value = 10 * COLORS.index(colors[0]) + 10 * COLORS.index(colors[1]) + COLORS.index(colors[2]) + value *= 10 ** COLORS.index(colors[3]) + value, unit = color_code(value) + return f'{value} {unit}, {COLORS_TOLERANCE[colors[4]]}%' + + +def color_code(color): + if color < 1000: + return color, 'ohms' + elif color < 1000000: + return color / 1000, 'kiloohms' + else: + return color / 1000000, 'megaohms' \ No newline at end of file diff --git a/exercises/practice/resistor-color-master/.meta/hi.json b/exercises/practice/resistor-color-master/.meta/hi.json new file mode 100644 index 0000000000..b25a6b1aca --- /dev/null +++ b/exercises/practice/resistor-color-master/.meta/hi.json @@ -0,0 +1,147 @@ +{ + "exercise": "resistor-color-master", + "cases": [ + { + "uuid": "8c4f9fb6-d477-4250-bc57-b325d2be226f", + "description": "Orange, orange, black, and red", + "property": "label", + "input": { + "colors": ["orange", "orange", "black", "red"] + }, + "expected": { + "value": 33, + "tolerance": 2, + "unit": "ohms" + } + }, + { + "uuid": "d1d4a769-9210-43cc-9a14-6af6ce4c0b00", + "description": "Blue, grey, brown, and violet", + "property": "label", + "input": { + "colors": ["blue", "grey", "brown", "violet"] + }, + "expected": { + "value": 680, + "tolerance": 0.1, + "unit": "ohms" + } + }, + { + "uuid": "6af91bc3-8275-4c38-920a-185d30feb5f3", + "description": "Red, black, red, and green", + "property": "label", + "input": { + "colors": ["red", "black", "red", "green"] + }, + "expected": { + "value": 2, + "tolerance": 0.5, + "unit": "kiloohms" + } + }, + { + "uuid": "9c4630bf-0dda-4212-baca-2f5111530b4d", + "description": "Green, brown, orange, and grey", + "property": "label", + "input": { + "colors": ["green", "brown", "orange", "grey"] + }, + "expected": { + "value": 51, + "tolerance": 0.05, + "unit": "kiloohms" + } + }, + { + "uuid": "93b7d0ec-39fb-49f3-bb88-2dd4b8c293af", + "description": "Yellow, violet, yellow, and blue", + "property": "label", + "input": { + "colors": ["yellow", "violet", "yellow", "blue"] + }, + "expected": { + "value": 470, + "tolerance": 0.25, + "unit": "kiloohms" + } + }, + { + "uuid": "5880ddf1-0dc6-4bd0-b9de-5626117cd2c7", + "description": "One black band", + "property": "label", + "input": { + "colors": ["black"] + }, + "expected": { + "value": 0, + "unit": "ohms" + } + }, + { + "uuid": "a5cfda34-3c02-4bda-b183-726791fb43b2", + "description": "Orange, orange, yellow, black, and brown", + "property": "label", + "input": { + "colors": ["orange", "orange", "yellow", "black", "brown"] + }, + "expected": { + "value": 334, + "tolerance": 1, + "unit": "ohms" + } + }, + { + "uuid": "4f0ad96c-cdab-4c84-95dd-7074e889e001", + "description": "Red, green, yellow, yellow, and brown", + "property": "label", + "input": { + "colors": ["red", "green", "yellow", "yellow", "brown"] + }, + "expected": { + "value": 2.54, + "tolerance": 1, + "unit": "megaohms" + } + }, + { + "uuid": "48c66841-208c-46a7-8992-1e84a2eda9e2", + "description": "Blue, grey, white, brown, and brown", + "property": "label", + "input": { + "colors": ["blue", "grey", "white", "brown", "brown"] + }, + "expected": { + "value": 6.89, + "tolerance": 1, + "unit": "kiloohms" + } + }, + { + "uuid": "4f3aeb0c-fd9d-4cbd-b204-554e61978f73", + "description": "Violet, orange, red, and grey", + "property": "label", + "input": { + "colors": ["violet", "orange", "red", "grey"] + }, + "expected": { + "value": 7.3, + "tolerance": 0.05, + "unit": "kiloohms" + } + }, + { + "uuid": "1ae3a17f-8bc0-449c-9831-fddfd2d91c5d", + "description": "Brown, red, orange, green, and blue", + "property": "label", + "input": { + "colors": ["brown", "red", "orange", "green", "blue"] + }, + "expected": { + "value": 12.3, + "tolerance": 0.25, + "unit": "megaohms" + } + } + ] +} diff --git a/exercises/practice/resistor-color-master/.meta/template.j2 b/exercises/practice/resistor-color-master/.meta/template.j2 new file mode 100644 index 0000000000..e9b458f34c --- /dev/null +++ b/exercises/practice/resistor-color-master/.meta/template.j2 @@ -0,0 +1,15 @@ +{%- import "generator_macros.j2" as macros with context -%} +{% macro test_case(case) -%} + {%- set input = case["input"] -%} + def test_{{ case["description"] | to_snake }}(self): + self.assertEqual( + {{ case["property"] | to_snake }}({{ case["input"]["colors"] }}), + "{{ case['expected']['value']}} {{ case['expected']['unit']}} {{case['expected']['tolerance']}}{{"%" if case['expected']['tolerance'] else ""}}" + ) +{%- endmacro %} +{{ macros.header()}} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + {{ test_case(case) }} + {% endfor %} diff --git a/exercises/practice/resistor-color-master/.meta/tests.toml b/exercises/practice/resistor-color-master/.meta/tests.toml new file mode 100644 index 0000000000..4f57723a19 --- /dev/null +++ b/exercises/practice/resistor-color-master/.meta/tests.toml @@ -0,0 +1,40 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[8c4f9fb6-d477-4250-bc57-b325d2be226f] +description = "Orange, orange, black, and red" + +[d1d4a769-9210-43cc-9a14-6af6ce4c0b00] +description = "Blue, grey, brown, and violet" + +[6af91bc3-8275-4c38-920a-185d30feb5f3] +description = "Red, black, red, and green" + +[9c4630bf-0dda-4212-baca-2f5111530b4d] +description = "Green, brown, orange, and grey" + +[5880ddf1-0dc6-4bd0-b9de-5626117cd2c7] +description = "One black band" + +[a5cfda34-3c02-4bda-b183-726791fb43b2] +description = "Orange, orange, yellow, black, and brown" + +[4f0ad96c-cdab-4c84-95dd-7074e889e001] +description = "Red, green, yellow, yellow, and brown" + +[48c66841-208c-46a7-8992-1e84a2eda9e2] +description = "Blue, grey, white, brown, and brown" + +[4f3aeb0c-fd9d-4cbd-b204-554e61978f73] +description = "Violet, orange, red, and grey" + +[1ae3a17f-8bc0-449c-9831-fddfd2d91c5d] +description = "Brown, red, orange, green, and blue" diff --git a/exercises/practice/resistor-color-master/resistor_color_master.py b/exercises/practice/resistor-color-master/resistor_color_master.py new file mode 100644 index 0000000000..83fd403e5c --- /dev/null +++ b/exercises/practice/resistor-color-master/resistor_color_master.py @@ -0,0 +1,49 @@ +COLORS = [ + 'black', + 'brown', + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'violet', + 'grey', + 'white' +] + +COLORS_TOLERANCE = { + 'brown': 1, + 'red': 2, + 'green': 0.5, + 'blue': 0.25, + 'violet': 0.1, + 'grey': 0.05, + 'gold': 5, + 'silver': 10 +} + + +def label(colors): + if len(colors) == 1: + return f'0 ohms' + elif len(colors) == 4: + value = 10 * COLORS.index(colors[0]) + COLORS.index(colors[1]) + value *= 10 ** COLORS.index(colors[2]) + value, unit = color_code(value) + value = int(value) if value.is_integer() else value + return f'{value} {unit} {COLORS_TOLERANCE[colors[3]]}%' + else: + value = 100 * COLORS.index(colors[0]) + 10 * COLORS.index(colors[1]) + COLORS.index(colors[2]) + value *= 10 ** COLORS.index(colors[3]) + value, unit = color_code(value) + value = int(value) if value.is_integer() else value + return f'{value} {unit} {COLORS_TOLERANCE[colors[4]]}%' + + +def color_code(color): + if color < 1000: + return color / 1, 'ohms' + elif color < 1000000: + return color / 1000, 'kiloohms' + else: + return color / 1000000, 'megaohms' \ No newline at end of file diff --git a/exercises/practice/resistor-color-master/resistor_color_master_test.py b/exercises/practice/resistor-color-master/resistor_color_master_test.py new file mode 100644 index 0000000000..458ca7ab60 --- /dev/null +++ b/exercises/practice/resistor-color-master/resistor_color_master_test.py @@ -0,0 +1,51 @@ +import unittest + +from resistor_color_master import ( + label, +) + +# Tests adapted from `problem-specifications//canonical-data.json` + + +class ResistorColorMasterTest(unittest.TestCase): + def test_orange_orange_black_and_red(self): + self.assertEqual(label(["orange", "orange", "black", "red"]), "33 ohms 2%") + + def test_blue_grey_brown_and_violet(self): + self.assertEqual(label(["blue", "grey", "brown", "violet"]), "680 ohms 0.1%") + + def test_red_black_red_and_green(self): + self.assertEqual(label(["red", "black", "red", "green"]), "2 kiloohms 0.5%") + + def test_green_brown_orange_and_grey(self): + self.assertEqual( + label(["green", "brown", "orange", "grey"]), "51 kiloohms 0.05%" + ) + + def test_one_black_band(self): + self.assertEqual(label(["black"]), "0 ohms") + + def test_orange_orange_yellow_black_and_brown(self): + self.assertEqual( + label(["orange", "orange", "yellow", "black", "brown"]), "334 ohms 1%" + ) + + def test_red_green_yellow_yellow_and_brown(self): + self.assertEqual( + label(["red", "green", "yellow", "yellow", "brown"]), "2.54 megaohms 1%" + ) + + def test_blue_grey_white_red_and_brown(self): + self.assertEqual( + label(["blue", "grey", "white", "red", "brown"]), "6.89 kiloohms 1%" + ) + + def test_violet_orange_red_and_grey(self): + self.assertEqual( + label(["violet", "orange", "red", "grey"]), "7.3 kiloohms 0.05%" + ) + + def test_brown_red_orange_green_and_blue(self): + self.assertEqual( + label(["brown", "red", "orange", "green", "blue"]), "12.3 megaohms 0.25%" + ) From 3494dcddb522cbb259d3661b98bfdcbaa86c5e57 Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 19:38:44 +0100 Subject: [PATCH 447/826] Fix --- config.json | 2 +- .../resistor-color-master/resistor_color_master_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 84c5f1ea6f..e9a513fafb 100644 --- a/config.json +++ b/config.json @@ -973,7 +973,7 @@ { "slug": "resistor-color-master", "name": "Resistor Color Master", - "uuid": "f987a5b7-96f2-49c2-99e8-aef30d23dd58", + "uuid": "8a738365-0efa-444f-9466-a757ddaddcdb", "practices": ["list-methods"], "prerequisites": [ "basics", diff --git a/exercises/practice/resistor-color-master/resistor_color_master_test.py b/exercises/practice/resistor-color-master/resistor_color_master_test.py index 458ca7ab60..cd595fe452 100644 --- a/exercises/practice/resistor-color-master/resistor_color_master_test.py +++ b/exercises/practice/resistor-color-master/resistor_color_master_test.py @@ -37,7 +37,7 @@ def test_red_green_yellow_yellow_and_brown(self): def test_blue_grey_white_red_and_brown(self): self.assertEqual( - label(["blue", "grey", "white", "red", "brown"]), "6.89 kiloohms 1%" + label(["blue", "grey", "white", "brown", "brown"]), "6.89 kiloohms 1%" ) def test_violet_orange_red_and_grey(self): From f168108f0e30a634e2aa01117c19b50b10a5fd12 Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 19:41:40 +0100 Subject: [PATCH 448/826] Fixed example --- .../practice/resistor-color-master/.meta/example.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/exercises/practice/resistor-color-master/.meta/example.py b/exercises/practice/resistor-color-master/.meta/example.py index a7dd1cd367..83fd403e5c 100644 --- a/exercises/practice/resistor-color-master/.meta/example.py +++ b/exercises/practice/resistor-color-master/.meta/example.py @@ -30,17 +30,19 @@ def label(colors): value = 10 * COLORS.index(colors[0]) + COLORS.index(colors[1]) value *= 10 ** COLORS.index(colors[2]) value, unit = color_code(value) - return f'{value} {unit}, {COLORS_TOLERANCE[colors[3]]}%' + value = int(value) if value.is_integer() else value + return f'{value} {unit} {COLORS_TOLERANCE[colors[3]]}%' else: - value = 10 * COLORS.index(colors[0]) + 10 * COLORS.index(colors[1]) + COLORS.index(colors[2]) + value = 100 * COLORS.index(colors[0]) + 10 * COLORS.index(colors[1]) + COLORS.index(colors[2]) value *= 10 ** COLORS.index(colors[3]) value, unit = color_code(value) - return f'{value} {unit}, {COLORS_TOLERANCE[colors[4]]}%' + value = int(value) if value.is_integer() else value + return f'{value} {unit} {COLORS_TOLERANCE[colors[4]]}%' def color_code(color): if color < 1000: - return color, 'ohms' + return color / 1, 'ohms' elif color < 1000000: return color / 1000, 'kiloohms' else: From 3df9a8008e8e9d776507b5cb16766b352856dcd2 Mon Sep 17 00:00:00 2001 From: Carl Date: Sun, 25 Dec 2022 19:42:39 +0100 Subject: [PATCH 449/826] Added instructions --- .../.docs/instructions.md | 72 +++++++++++++------ 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/exercises/practice/resistor-color-master/.docs/instructions.md b/exercises/practice/resistor-color-master/.docs/instructions.md index fcc76958a5..8263897d07 100644 --- a/exercises/practice/resistor-color-master/.docs/instructions.md +++ b/exercises/practice/resistor-color-master/.docs/instructions.md @@ -1,7 +1,7 @@ -# Instructions +# Description If you want to build something using a Raspberry Pi, you'll probably use _resistors_. -For this exercise, you need to know only three things about them: +For this exercise, you need to be able to use 1, 4, and 5 bands resistor: - Each resistor has a resistance value. - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. @@ -9,7 +9,7 @@ For this exercise, you need to know only three things about them: - Each band acts as a digit of a number. For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. - The program will take 3 colors as input, and outputs the correct value, in ohms. + The program will take 1, 4, or 5 colors as input, and outputs the correct value, in ohms. The color bands are encoded as follows: - Black: 0 @@ -23,32 +23,64 @@ For this exercise, you need to know only three things about them: - Grey: 8 - White: 9 -In `resistor-color duo` you decoded the first two colors. -For instance: orange-orange got the main value `33`. -The third color stands for how many zeros need to be added to the main value. -The main value plus the zeros gives us a value in ohms. -For the exercise it doesn't matter what ohms really are. -For example: +In `resistor-color trio` you decoded the first three colors. +For instance: orange-orange-brown got the main value `330`. +In this exercise you need to add tolerance to the mix. +Tolerance is the maximum amount that the value can be above or below the main value. +The tolerance value is always the last band. +For example, if the last band is green, the maximum tolerance will be 0.5%. +The tolerance band will have one of these values: -- orange-orange-black would be 33 and no zeros, which becomes 33 ohms. -- orange-orange-red would be 33 and 2 zeros, which becomes 3300 ohms. -- orange-orange-orange would be 33 and 3 zeros, which becomes 33000 ohms. +- Grey - 0.05% +- Violet - 0.1% +- Blue - 0.25% +- Green - 0.5% +- Brown - 1% +- Red - 2% +- Gold - 5% +- Silver - 10% -(If Math is your thing, you may want to think of the zeros as exponents of 10. -If Math is not your thing, go with the zeros. -It really is the same thing, just in plain English instead of Math lingo.) +The fourth band resistor is built up like these: + +| Band_1 | Band_2 | Band_3 | band_4 | +| ------- | ------- | ---------- | --------- | +| Value_1 | Value_2 | Multiplier | Tolerance | + +Meaning + +- orange-orange-brown-green would be 330 ohms with a 0.5% tolerance. +- orange-orange-red would-grey would be 3300 ohms with 0.005 tolerance. + +The difference between the 4 and 5 band resistor is that there is an extra band for value a more precises value. + +| Band_1 | Band_2 | Band_3 | Band_4 | band_5 | +| ------- | ------- | ------- | ---------- | --------- | +| Value_1 | Value_2 | Value_3 | Multiplier | Tolerance | + +Meaning + +- orange-orange-orange-black-green would be 330 ohms with a 0.5% tolerance. + +There will also be a one band resistor. +This resistor will only have the color black and have the value 0. This exercise is about translating the colors into a label: -> "... ohms" +> "... ohms ...%" -So an input of `"orange", "orange", "black"` should return: +So an input of `"orange", "orange", "black, green"` should return: -> "33 ohms" +> "33 ohms 0.5%" When we get more than a thousand ohms, we say "kiloohms". That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. -So an input of `"orange", "orange", "orange"` should return: +So an input of `"orange", "orange", "orange", grey` should return: + +> "33 kiloohms 0.05%" + +When we get more than a million ohms, we say "megaohms". + +So an input of `"orange", "orange", "orange", "red"` should return: -> "33 kiloohms" +> "33 megaohms 2%" From 4e60f5829d11048b847bdc73cc5c6d39b2ef502e Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 00:26:04 +0100 Subject: [PATCH 450/826] Changes --- concepts/itertools/.meta/config.json | 4 +- concepts/itertools/about.md | 341 +----------------- concepts/itertools/links.json | 4 +- .../resistor-color-master/.meta/template.j2 | 6 +- 4 files changed, 10 insertions(+), 345 deletions(-) diff --git a/concepts/itertools/.meta/config.json b/concepts/itertools/.meta/config.json index 3bf2b514cc..65190cc1ef 100644 --- a/concepts/itertools/.meta/config.json +++ b/concepts/itertools/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "meatball133"], + "authors": ["bethanyg", "cmccandless"], "contributors": [] -} +} \ No newline at end of file diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md index 366515cf1a..cbc5cd89d9 100644 --- a/concepts/itertools/about.md +++ b/concepts/itertools/about.md @@ -1,340 +1 @@ -## Iterators terminating on the shortest input sequence - -### Chain() - -`chain(iterable1, iterable2...)` creates an irretator of values from the iterables in the order they are given. - -```python ->>> import itertools ->>> for number in itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9]): -... print(number, end=' ') -... -1 2 3 4 5 6 7 8 9 -``` - -Chain can also be used to concate a different amount of iterables to a list or tuple by using the `list()` or `tuple()` function. - -Using `list()`: - -```python ->>> import itertools ->>> list(itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9])) -[1, 2, 3, 4, 5, 6, 7, 8, 9] -``` - -Using `tuple()`: - -```python ->>> import itertools ->>> tuple(itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9])) -(1, 2, 3, 4, 5, 6, 7, 8, 9) -``` - -### chain.from_iterable() - -Works like chain but takes a single nested iterable, unpacking that iterable into individual iterables. - -```python ->>> import itertools ->>> for number in itertools.chain.from_iterable( - [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9] - ]): -... print(number, end=' ') -... -1 2 3 4 5 6 7 8 9 -``` - -### Compress() - -`compress(iterable, selectors)` creates an iterator from the input iterable where the corresponding selector is `True`. -The selector can be `True`/`False` or integers, where 0 is `False` and 1 is `True`. - -```python ->>> import itertools ->>> for letter in itertools.compress("Exercism", [1, 0, 0, 1, 1, 0, 1, 1]): -... print(letter, end=' ') -... -E r c s m -``` - -With `True`/`False`: - -```python ->>> import itertools ->>> for letter in itertools.compress("Exercism", [True, False, False, True, True, False, True, True]): -... print(letter, end=' ') -... -E r c s m -``` - -### Islice() - -`islice(iterable, start, , )` creates a new iterator from the slice (from the start index to the stop index with a step size of step). - -```python ->>> import itertools ->>> for letter in itertools.islice("Exercism", 2, 5, 2): -... print(letter, end=' ') -... -e c -``` - -### Pairwise() - -```exercism/note -`Pairwise()` requires Python 3.10+. -If you are using the online editor then you don't need to worry about this. -``` - -`Pairwise(iterable)` was intruduced in Python 3.10 and returns an iterator of overlapping pairs of values from the input iterable. - -```python ->>> import itertools ->>> for pair in itertools.pairwise("Exercism"): -... print(pair, end=' ') -... -('E', 'x') ('x', 'e') ('e', 'r') ('r', 'c') ('c', 'i') ('i', 's') ('s', 'm') -``` - -### Tee() - -Talk with Bethany about - -### Zip_longest() - -#### Explaning zip - -```exercism/caution -Pythons `zip()` function should not be confused with the zip compression format. -``` - -`zip()` is a built in function and is not apart of the `itertools` module. -It takes any number of iterables and returns an iterator of tuples. -Where the i-th tuple contains the i-th element from each of the argument iterables. -For example, the first tuple will contain the first element from each iterable, the second tuple will contain the second element from each iterable, and so on until the shortest iterable is exhausted. - -```python ->>> zipped = zip(['x', 'y', 'z'], [1, 2, 3], [True, False, True]) ->>> list(zipped) -[('x', 1, True),('y', 2, False), ('z', 3, True)] -``` - -If the iterables are not the same length, then the iterator will stop when the shortest iterable is exhausted. - -```python ->>> zipped = zip(['x', 'y', 'z'], [1, 2, 3, 4], [True, False]) ->>> list(zipped) -[('x', 1, True),('y', 2, False)] -``` - -#### Explaning zip_longest - -`zip_longest(iterator, )` is a function from the `itertools` module. -Unlink `zip()`, it will not stop when the shortest iterable is exhausted. -If the iterables are not the same length, `fillvalue` will be used to pad missing values. -By the default the `fillvalue` is `None`. - -```python ->>> import itertools ->>> zipped = itertools.zip_longest(['x', 'y', 'z'], [1, 2, 3, 4], [True, False]) ->>> list(zipped) -[('x', 1, True),('y', 2, False), ('z', 3, None), (None, 4, None)] -``` - -An example where a fillvalue is given: - -```python ->>> import itertools ->>> zipped = itertools.zip_longest(['x', 'y', 'z'], [1, 2, 3, 4], [True, False], fillvalue='fill') ->>> list(zipped) -[('x', 1, True),('y', 2, False), ('z', 3, 'fill'), ('fill', 4, 'fill')] -``` - -## Combinatoric iterators - -### Product() - -`product(iterable1, iterable2..., )` creates an iterator of tuples where the i-th tuple contains the i-th element from each of the argument iterables. -The repeat keyword argument can be used to specify the number of times the input iterables are repeated. -By default the repeat keyword argument is 1. - -```python ->>> import itertools ->>> for product in itertools.product("ABCD", repeat=1): -... print(product, end=' ') -... -('A',) ('B',) ('C',) ('D',) -``` - -Giving a repeat value of 2: - -```python ->>> import itertools ->>> for product in itertools.product("ABCD", repeat=2): -... print(product, end=' ') -... -('A', 'A') ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'A') ('B', 'B') ('B', 'C') ('B', 'D') ('C', 'A') ('C', 'B') ('C', 'C') ('C', 'D') ('D', 'A') ('D', 'B') ('D', 'C') ('D', 'D') -``` - -The last one here can be seen as doing a nested for loop. -When you increase the repeat value the number of iterations increases exponentially. -The example above is a n\*\*2 iteration. - -```python ->>> import itertools ->>> for letter1 in "ABCD": -... for letter2 in "ABCD": -... print((letter1, letter2), end=' ') -... -('A', 'A') ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'A') ('B', 'B') ('B', 'C') ('B', 'D') ('C', 'A') ('C', 'B') ('C', 'C') ('C', 'D') ('D', 'A') ('D', 'B') ('D', 'C') ('D', 'D') -``` - -You can also give it multiple iterables. - -```python ->>> import itertools ->>> for product in itertools.product("ABCD", "xy" repeat=1): -... print(product, end=' ') -... -('A', 'x') ('A', 'y') ('B', 'x') ('B', 'y') ('C', 'x') ('C', 'y') ('D', 'x') ('D', 'y') -``` - -Here is an example of doing it wihout `product()`. -It looks similliar to the last example but since we have two iterables we need to nest the for loops. -Even though the proudct is given repeat=1. -The reasson to why it is only 2 for loops earlier was because we only had one iterable. -If we had two iterables and gave it repeat=2 we would need 4 for loops. -Since 2 \* 2 = 4. - -```python ->>> for letter1 in "ABCD": -... for letter2 in "xy": -... print((letter1, letter2), end=' ') -... -('A', 'x') ('A', 'y') ('B', 'x') ('B', 'y') ('C', 'x') ('C', 'y') ('D', 'x') ('D', 'y') -``` - -### Permutations() - -`permutations(iterable, )` creates an iterator of tuples. -It works like `product()` but it doesnt repeat values from a specific positoon from the iterable and can only take one iterable. -The "r" keyword argument can be used to specify the number of times the input iterables are repeated. -By default the "r" keyword argument is None. -If "r" is None then the length of the iterable is used. - -```python ->>> import itertools ->>> for permutation in itertools.permutations("ABCD", repeat=1): -... print(permutation, end=' ') -... -('A',) ('B',) ('C',) ('D',) -``` - -```python ->>> import itertools ->>> for permutation in itertools.permutations("ABCD", repeat=2): -... print(permutation, end=' ') -... -('A', 'B') ('A', 'C') ('A', 'D') ('B', 'A') ('B', 'C') ('B', 'D') ('C', 'A') ('C', 'B') ('C', 'D') ('D', 'A') ('D', 'B') ('D', 'C') -``` - -### Combinations() - -`combinations(iterable, r)` finds all the possible combinations of the given iterable. -The r keyword argument is used to specify the length of the tuples generated. - -```python ->>> import itertools ->>> for combination in itertools.combinations("ABCD", 2): -... print(combination, end=' ') -... -('A', 'B') ('A', 'C') ('A', 'D') ('B', 'C') ('B', 'D') ('C', 'D') -``` - -### Combinations_with_replacement() - -`combinations_with_replacement(iterable, r)` finds all the possible combinations of the given iterable. -The r keyword argument is used to specify the length of the tuples generated. -The difference between this and `combinations()` is that it can repeat values. - -```python ->>> import itertools ->>> for combination in itertools.combinations_with_replacement("ABCD", 2): -... print(combination, end=' ') -... -('A', 'A') ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'B') ('B', 'C') ('B', 'D') ('C', 'C') ('C', 'D') ('D', 'D') -``` - -## Infinite iterators - -Most of iterator from the `itertools` module get exhausted after a time. -But there are some that are infinite, these are known as infinte iterators. -These iterators will will keep producing values until you tell them to stop. - -```exercism/note -To avoid infinite loops, you can use `break` to end a loop. -``` - -### Count() - -`count(start, )` produces all values from the start value to infinity. -Count also has an optional step parameter, which will produce values with a step size other than 1. - -```python ->>> import itertools ->>> for number in itertools.count(5, 2): -... if number > 20: -... break -... else: -... print(number, end=' ') -... -5 7 9 11 13 15 17 19 -``` - -Giving `count()` a negative step size will produces values in a descending order. - -```python ->>> import itertools ->>> for number in itertools.count(5, -2): -... if number < -20: -... break -... else: -... print(number, end=' ') -... -5 3 1 -1 -3 -5 -7 -9 -11 -13 -15 -17 -19 -``` - -### Cycle() - -`cycle(iterable)` produces all values from the iterable in an infinte loop. -A `list`, `tuple`, `string`, `dict` or any other iterable can be used. - -```python ->>> import itertools ->>> number = 0 ->>> for letter in itertools.cycle("ABC"): -... if number == 10: -... break -... else: -... print(letter, end=' ') -... number += 1 -... -A B C A B C A B C A -``` - -### Repeat() - -`repeat(object, )` produces the same value in an infinte loop. -Although if the optional times parameter is given, the value will produces that many times. -Meaning that it is not an infinite loop if that parameter is given. - -```python ->>> import itertools ->>> for number in itertools.repeat(5, 3): -... print(number, end=' ') -... -5 5 5 -``` +#TODO: Add about for this concept. diff --git a/concepts/itertools/links.json b/concepts/itertools/links.json index 8a44906b3f..eb5fb7c38a 100644 --- a/concepts/itertools/links.json +++ b/concepts/itertools/links.json @@ -1,7 +1,7 @@ [ { - "url": "https://docs.python.org/3/library/itertools.htm", - "description": "Offical Python documentation for the itertools module." + "url": "http://example.com/", + "description": "TODO: add new link (above) and write a short description here of the resource." }, { "url": "http://example.com/", diff --git a/exercises/practice/resistor-color-master/.meta/template.j2 b/exercises/practice/resistor-color-master/.meta/template.j2 index e9b458f34c..86f9580c5f 100644 --- a/exercises/practice/resistor-color-master/.meta/template.j2 +++ b/exercises/practice/resistor-color-master/.meta/template.j2 @@ -4,7 +4,11 @@ def test_{{ case["description"] | to_snake }}(self): self.assertEqual( {{ case["property"] | to_snake }}({{ case["input"]["colors"] }}), - "{{ case['expected']['value']}} {{ case['expected']['unit']}} {{case['expected']['tolerance']}}{{"%" if case['expected']['tolerance'] else ""}}" + {%- if case['expected']['tolerance'] %} + "{{ case['expected']['value']}} {{ case['expected']['unit']}} {{case['expected']['tolerance']}}%" + {%- else -%} + "{{ case['expected']['value']}} {{ case['expected']['unit']}}" + {% endif %} ) {%- endmacro %} {{ macros.header()}} From fe65f19b1cb4892ecdd72a08d160835073af25c3 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 26 Dec 2022 00:26:59 +0100 Subject: [PATCH 451/826] Delete about.md --- concepts/itertools/about.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 concepts/itertools/about.md diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md deleted file mode 100644 index cbc5cd89d9..0000000000 --- a/concepts/itertools/about.md +++ /dev/null @@ -1 +0,0 @@ -#TODO: Add about for this concept. From 4afba06f43420b7319489d6ef0a6e226ec36b042 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 26 Dec 2022 00:27:09 +0100 Subject: [PATCH 452/826] Delete config.json --- concepts/itertools/.meta/config.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 concepts/itertools/.meta/config.json diff --git a/concepts/itertools/.meta/config.json b/concepts/itertools/.meta/config.json deleted file mode 100644 index 65190cc1ef..0000000000 --- a/concepts/itertools/.meta/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], - "contributors": [] -} \ No newline at end of file From 9374285a09f3b7556ca2b1b4ec61fd5a46929168 Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 00:29:55 +0100 Subject: [PATCH 453/826] some stuff --- concepts/itertools/.meta/config.json | 5 +++++ concepts/itertools/introduction copy.md | 1 + 2 files changed, 6 insertions(+) create mode 100644 concepts/itertools/.meta/config.json create mode 100644 concepts/itertools/introduction copy.md diff --git a/concepts/itertools/.meta/config.json b/concepts/itertools/.meta/config.json new file mode 100644 index 0000000000..9b9e8da5a9 --- /dev/null +++ b/concepts/itertools/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "TODO: add blurb for this concept", + "authors": ["bethanyg", "cmccandless"], + "contributors": [] +} diff --git a/concepts/itertools/introduction copy.md b/concepts/itertools/introduction copy.md new file mode 100644 index 0000000000..bbe12ffd5e --- /dev/null +++ b/concepts/itertools/introduction copy.md @@ -0,0 +1 @@ +#TODO: Add introduction for this concept. From b4e47d3f0747be0658daaa9b1e7423c1a5b6d951 Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 00:30:37 +0100 Subject: [PATCH 454/826] some more --- concepts/itertools/{introduction copy.md => about.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename concepts/itertools/{introduction copy.md => about.md} (100%) diff --git a/concepts/itertools/introduction copy.md b/concepts/itertools/about.md similarity index 100% rename from concepts/itertools/introduction copy.md rename to concepts/itertools/about.md From ac0acdde7f03a899297275cc1da867fa436f6727 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 26 Dec 2022 00:31:42 +0100 Subject: [PATCH 455/826] Update about.md --- concepts/itertools/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md index bbe12ffd5e..cbc5cd89d9 100644 --- a/concepts/itertools/about.md +++ b/concepts/itertools/about.md @@ -1 +1 @@ -#TODO: Add introduction for this concept. +#TODO: Add about for this concept. From 9d73b67a7e9a6408f443eae3052a674da0c0070c Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 26 Dec 2022 00:31:53 +0100 Subject: [PATCH 456/826] Update about.md --- concepts/itertools/about.md | 1 + 1 file changed, 1 insertion(+) diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md index cbc5cd89d9..c628150d56 100644 --- a/concepts/itertools/about.md +++ b/concepts/itertools/about.md @@ -1 +1,2 @@ #TODO: Add about for this concept. + From 03216728fc80b1b48f11a8b01b37ac4d9e9acd14 Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 00:33:31 +0100 Subject: [PATCH 457/826] fix --- .../resistor-color-master/.meta/{hi.json => canonical-data.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exercises/practice/resistor-color-master/.meta/{hi.json => canonical-data.json} (100%) diff --git a/exercises/practice/resistor-color-master/.meta/hi.json b/exercises/practice/resistor-color-master/.meta/canonical-data.json similarity index 100% rename from exercises/practice/resistor-color-master/.meta/hi.json rename to exercises/practice/resistor-color-master/.meta/canonical-data.json From 85dcb967f51b71ab62e1ae20c1faa5beabf46d14 Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 00:59:27 +0100 Subject: [PATCH 458/826] Updates --- .../resistor-color-master/.meta/template.j2 | 2 +- .../resistor_color_master.py | 49 +------------------ .../resistor_color_master_test.py | 18 +++---- 3 files changed, 11 insertions(+), 58 deletions(-) diff --git a/exercises/practice/resistor-color-master/.meta/template.j2 b/exercises/practice/resistor-color-master/.meta/template.j2 index 86f9580c5f..b49a7f2fd1 100644 --- a/exercises/practice/resistor-color-master/.meta/template.j2 +++ b/exercises/practice/resistor-color-master/.meta/template.j2 @@ -5,7 +5,7 @@ self.assertEqual( {{ case["property"] | to_snake }}({{ case["input"]["colors"] }}), {%- if case['expected']['tolerance'] %} - "{{ case['expected']['value']}} {{ case['expected']['unit']}} {{case['expected']['tolerance']}}%" + "{{ case['expected']['value']}} {{ case['expected']['unit']}} ±{{case['expected']['tolerance']}}%" {%- else -%} "{{ case['expected']['value']}} {{ case['expected']['unit']}}" {% endif %} diff --git a/exercises/practice/resistor-color-master/resistor_color_master.py b/exercises/practice/resistor-color-master/resistor_color_master.py index 83fd403e5c..a4b658ecdd 100644 --- a/exercises/practice/resistor-color-master/resistor_color_master.py +++ b/exercises/practice/resistor-color-master/resistor_color_master.py @@ -1,49 +1,2 @@ -COLORS = [ - 'black', - 'brown', - 'red', - 'orange', - 'yellow', - 'green', - 'blue', - 'violet', - 'grey', - 'white' -] - -COLORS_TOLERANCE = { - 'brown': 1, - 'red': 2, - 'green': 0.5, - 'blue': 0.25, - 'violet': 0.1, - 'grey': 0.05, - 'gold': 5, - 'silver': 10 -} - - def label(colors): - if len(colors) == 1: - return f'0 ohms' - elif len(colors) == 4: - value = 10 * COLORS.index(colors[0]) + COLORS.index(colors[1]) - value *= 10 ** COLORS.index(colors[2]) - value, unit = color_code(value) - value = int(value) if value.is_integer() else value - return f'{value} {unit} {COLORS_TOLERANCE[colors[3]]}%' - else: - value = 100 * COLORS.index(colors[0]) + 10 * COLORS.index(colors[1]) + COLORS.index(colors[2]) - value *= 10 ** COLORS.index(colors[3]) - value, unit = color_code(value) - value = int(value) if value.is_integer() else value - return f'{value} {unit} {COLORS_TOLERANCE[colors[4]]}%' - - -def color_code(color): - if color < 1000: - return color / 1, 'ohms' - elif color < 1000000: - return color / 1000, 'kiloohms' - else: - return color / 1000000, 'megaohms' \ No newline at end of file +pass diff --git a/exercises/practice/resistor-color-master/resistor_color_master_test.py b/exercises/practice/resistor-color-master/resistor_color_master_test.py index cd595fe452..41f5a1f11a 100644 --- a/exercises/practice/resistor-color-master/resistor_color_master_test.py +++ b/exercises/practice/resistor-color-master/resistor_color_master_test.py @@ -9,17 +9,17 @@ class ResistorColorMasterTest(unittest.TestCase): def test_orange_orange_black_and_red(self): - self.assertEqual(label(["orange", "orange", "black", "red"]), "33 ohms 2%") + self.assertEqual(label(["orange", "orange", "black", "red"]), "33 ohms ±2%") def test_blue_grey_brown_and_violet(self): - self.assertEqual(label(["blue", "grey", "brown", "violet"]), "680 ohms 0.1%") + self.assertEqual(label(["blue", "grey", "brown", "violet"]), "680 ohms ±0.1%") def test_red_black_red_and_green(self): - self.assertEqual(label(["red", "black", "red", "green"]), "2 kiloohms 0.5%") + self.assertEqual(label(["red", "black", "red", "green"]), "2 kiloohms ±0.5%") def test_green_brown_orange_and_grey(self): self.assertEqual( - label(["green", "brown", "orange", "grey"]), "51 kiloohms 0.05%" + label(["green", "brown", "orange", "grey"]), "51 kiloohms ±0.05%" ) def test_one_black_band(self): @@ -27,25 +27,25 @@ def test_one_black_band(self): def test_orange_orange_yellow_black_and_brown(self): self.assertEqual( - label(["orange", "orange", "yellow", "black", "brown"]), "334 ohms 1%" + label(["orange", "orange", "yellow", "black", "brown"]), "334 ohms ±1%" ) def test_red_green_yellow_yellow_and_brown(self): self.assertEqual( - label(["red", "green", "yellow", "yellow", "brown"]), "2.54 megaohms 1%" + label(["red", "green", "yellow", "yellow", "brown"]), "2.54 megaohms ±1%" ) def test_blue_grey_white_red_and_brown(self): self.assertEqual( - label(["blue", "grey", "white", "brown", "brown"]), "6.89 kiloohms 1%" + label(["blue", "grey", "white", "red", "brown"]), "6.89 kiloohms ±1%" ) def test_violet_orange_red_and_grey(self): self.assertEqual( - label(["violet", "orange", "red", "grey"]), "7.3 kiloohms 0.05%" + label(["violet", "orange", "red", "grey"]), "7.3 kiloohms ±0.05%" ) def test_brown_red_orange_green_and_blue(self): self.assertEqual( - label(["brown", "red", "orange", "green", "blue"]), "12.3 megaohms 0.25%" + label(["brown", "red", "orange", "green", "blue"]), "12.3 megaohms ±0.25%" ) From 0baf1fedae7fe0a501c441037b427ba50e491a9c Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 01:01:45 +0100 Subject: [PATCH 459/826] fix --- .../resistor-color-master/resistor_color_master_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/resistor-color-master/resistor_color_master_test.py b/exercises/practice/resistor-color-master/resistor_color_master_test.py index 41f5a1f11a..27ac0f82a8 100644 --- a/exercises/practice/resistor-color-master/resistor_color_master_test.py +++ b/exercises/practice/resistor-color-master/resistor_color_master_test.py @@ -37,7 +37,7 @@ def test_red_green_yellow_yellow_and_brown(self): def test_blue_grey_white_red_and_brown(self): self.assertEqual( - label(["blue", "grey", "white", "red", "brown"]), "6.89 kiloohms ±1%" + label(["blue", "grey", "white", "brown", "brown"]), "6.89 kiloohms ±1%" ) def test_violet_orange_red_and_grey(self): From ae349aeed63ca425fe2f1a813974dee51d99c36d Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 01:03:42 +0100 Subject: [PATCH 460/826] fix --- .../practice/resistor-color-master/resistor_color_master.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/resistor-color-master/resistor_color_master.py b/exercises/practice/resistor-color-master/resistor_color_master.py index a4b658ecdd..1d36841cf1 100644 --- a/exercises/practice/resistor-color-master/resistor_color_master.py +++ b/exercises/practice/resistor-color-master/resistor_color_master.py @@ -1,2 +1,2 @@ def label(colors): -pass + pass From 216d0ad83e2bf311dd3047f5cc21d698f85732ac Mon Sep 17 00:00:00 2001 From: Carl Date: Mon, 26 Dec 2022 01:05:51 +0100 Subject: [PATCH 461/826] Fix --- exercises/practice/resistor-color-master/.meta/example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/resistor-color-master/.meta/example.py b/exercises/practice/resistor-color-master/.meta/example.py index 83fd403e5c..b0d1d612b9 100644 --- a/exercises/practice/resistor-color-master/.meta/example.py +++ b/exercises/practice/resistor-color-master/.meta/example.py @@ -31,13 +31,13 @@ def label(colors): value *= 10 ** COLORS.index(colors[2]) value, unit = color_code(value) value = int(value) if value.is_integer() else value - return f'{value} {unit} {COLORS_TOLERANCE[colors[3]]}%' + return f'{value} {unit} ±{COLORS_TOLERANCE[colors[3]]}%' else: value = 100 * COLORS.index(colors[0]) + 10 * COLORS.index(colors[1]) + COLORS.index(colors[2]) value *= 10 ** COLORS.index(colors[3]) value, unit = color_code(value) value = int(value) if value.is_integer() else value - return f'{value} {unit} {COLORS_TOLERANCE[colors[4]]}%' + return f'{value} {unit} ±{COLORS_TOLERANCE[colors[4]]}%' def color_code(color): From b47657bf90ed025f33f3a7cabc82aebc52e4a5e5 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 15 Jun 2023 21:16:37 +0200 Subject: [PATCH 462/826] Renamed the exercise --- config.json | 4 +- .../.docs/instructions.md | 76 +++++++++ .../.docs/introduction.md | 10 ++ .../resistor-color-expert/.meta/config.json | 18 +++ .../.meta/example.py | 2 +- .../.meta/tests.toml | 0 .../resistor_color_expert.py | 2 + .../resistor_color_expert_test.py | 51 ++++++ .../.docs/instructions.md | 86 ---------- .../.meta/canonical-data.json | 147 ------------------ .../resistor-color-master/.meta/config.json | 20 --- .../resistor-color-master/.meta/template.j2 | 19 --- .../resistor_color_master.py | 2 - .../resistor_color_master_test.py | 51 ------ 14 files changed, 160 insertions(+), 328 deletions(-) create mode 100644 exercises/practice/resistor-color-expert/.docs/instructions.md create mode 100644 exercises/practice/resistor-color-expert/.docs/introduction.md create mode 100644 exercises/practice/resistor-color-expert/.meta/config.json rename exercises/practice/{resistor-color-master => resistor-color-expert}/.meta/example.py (97%) rename exercises/practice/{resistor-color-master => resistor-color-expert}/.meta/tests.toml (100%) create mode 100644 exercises/practice/resistor-color-expert/resistor_color_expert.py create mode 100644 exercises/practice/resistor-color-expert/resistor_color_expert_test.py delete mode 100644 exercises/practice/resistor-color-master/.docs/instructions.md delete mode 100644 exercises/practice/resistor-color-master/.meta/canonical-data.json delete mode 100644 exercises/practice/resistor-color-master/.meta/config.json delete mode 100644 exercises/practice/resistor-color-master/.meta/template.j2 delete mode 100644 exercises/practice/resistor-color-master/resistor_color_master.py delete mode 100644 exercises/practice/resistor-color-master/resistor_color_master_test.py diff --git a/config.json b/config.json index e9a513fafb..e8c7f2f505 100644 --- a/config.json +++ b/config.json @@ -971,8 +971,8 @@ "difficulty": 3 }, { - "slug": "resistor-color-master", - "name": "Resistor Color Master", + "slug": "resistor-color-expert", + "name": "Resistor Color Expert", "uuid": "8a738365-0efa-444f-9466-a757ddaddcdb", "practices": ["list-methods"], "prerequisites": [ diff --git a/exercises/practice/resistor-color-expert/.docs/instructions.md b/exercises/practice/resistor-color-expert/.docs/instructions.md new file mode 100644 index 0000000000..f08801644c --- /dev/null +++ b/exercises/practice/resistor-color-expert/.docs/instructions.md @@ -0,0 +1,76 @@ +# Instructions + +In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. +The program will take 1, 4, or 5 colors as input, and outputs the correct value, in ohms. +The color bands are encoded as follows: + +- Black: 0 +- Brown: 1 +- Red: 2 +- Orange: 3 +- Yellow: 4 +- Green: 5 +- Blue: 6 +- Violet: 7 +- Grey: 8 +- White: 9 + +In `resistor-color trio` you decoded the first three colors. +For instance: orange-orange-brown translated to the main value `330`. +In this exercise you will need to add _tolerance_ to the mix. +Tolerance is the maximum amount that a value can be above or below the main value. +For example, if the last band is green, the maximum tolerance will be ±0.5%. + +The tolerance band will have one of these values: + +- Grey - 0.05% +- Violet - 0.1% +- Blue - 0.25% +- Green - 0.5% +- Brown - 1% +- Red - 2% +- Gold - 5% +- Silver - 10% + +The four-band resistor is built up like this: + +| Band_1 | Band_2 | Band_3 | band_4 | +| ------- | ------- | ---------- | --------- | +| Value_1 | Value_2 | Multiplier | Tolerance | + +Meaning + +- orange-orange-brown-green would be 330 ohms with a ±0.5% tolerance. +- orange-orange-red would-grey would be 3300 ohms with ±0.005% tolerance. +The difference between a four and five-band resistor is that the five-band resistor has an extra band to indicate a more precise value. + +| Band_1 | Band_2 | Band_3 | Band_4 | band_5 | +| ------- | ------- | ------- | ---------- | --------- | +| Value_1 | Value_2 | Value_3 | Multiplier | Tolerance | + +Meaning + +- orange-orange-orange-black-green would be 330 ohms with a ±0.5% tolerance. +There are also one band resistors. +This type of resistor only has the color black and has a value of 0. + +This exercise is about translating the resistor band colors into a label: + +"... ohms ...%" + +So an input of "orange", "orange", "black, green" should return: + +"33 ohms ±0.5%" + +When there are more than a thousand ohms, we say "kiloohms". + That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. + +So an input of "orange", "orange", "orange", grey should return: + +"33 kiloohms ±0.05%" + +When there are more than a million ohms, we say "megaohms". + +So an input of "orange", "orange", "orange", "red" should return: + +"33 megaohms ±2%" diff --git a/exercises/practice/resistor-color-expert/.docs/introduction.md b/exercises/practice/resistor-color-expert/.docs/introduction.md new file mode 100644 index 0000000000..1c95695d13 --- /dev/null +++ b/exercises/practice/resistor-color-expert/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +If you want to build something using a Raspberry Pi, you'll probably use _resistors_. +For this exercise, you need to be able to use 1, 4, and 5 band resistors: + +- Each resistor has a resistance value. +- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. + To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. +- Each band acts as a digit of a number. + For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. diff --git a/exercises/practice/resistor-color-expert/.meta/config.json b/exercises/practice/resistor-color-expert/.meta/config.json new file mode 100644 index 0000000000..66d1121b6a --- /dev/null +++ b/exercises/practice/resistor-color-expert/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "meatball133", + "bethanyg" + ], + "files": { + "solution": [ + "resistor_color_expert.py" + ], + "test": [ + "resistor_color_expert_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Convert color codes, as used on resistors with different amounts of bands, to a human-readable label." +} diff --git a/exercises/practice/resistor-color-master/.meta/example.py b/exercises/practice/resistor-color-expert/.meta/example.py similarity index 97% rename from exercises/practice/resistor-color-master/.meta/example.py rename to exercises/practice/resistor-color-expert/.meta/example.py index b0d1d612b9..0fd6e42fcd 100644 --- a/exercises/practice/resistor-color-master/.meta/example.py +++ b/exercises/practice/resistor-color-expert/.meta/example.py @@ -23,7 +23,7 @@ } -def label(colors): +def resistor_label(colors): if len(colors) == 1: return f'0 ohms' elif len(colors) == 4: diff --git a/exercises/practice/resistor-color-master/.meta/tests.toml b/exercises/practice/resistor-color-expert/.meta/tests.toml similarity index 100% rename from exercises/practice/resistor-color-master/.meta/tests.toml rename to exercises/practice/resistor-color-expert/.meta/tests.toml diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert.py b/exercises/practice/resistor-color-expert/resistor_color_expert.py new file mode 100644 index 0000000000..f36881c5a0 --- /dev/null +++ b/exercises/practice/resistor-color-expert/resistor_color_expert.py @@ -0,0 +1,2 @@ +def resistor_label(colors): + pass diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py new file mode 100644 index 0000000000..8d17a36409 --- /dev/null +++ b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py @@ -0,0 +1,51 @@ +import unittest + +from resistor_color_expert import ( + resistor_label, +) + +# Tests adapted from `problem-specifications//canonical-data.json` + + +class ResistorColorMasterTest(unittest.TestCase): + def test_orange_orange_black_and_red(self): + self.assertEqual(resistor_label(["orange", "orange", "black", "red"]), "33 ohms ±2%") + + def test_blue_grey_brown_and_violet(self): + self.assertEqual(resistor_label(["blue", "grey", "brown", "violet"]), "680 ohms ±0.1%") + + def test_red_black_red_and_green(self): + self.assertEqual(resistor_label(["red", "black", "red", "green"]), "2 kiloohms ±0.5%") + + def test_green_brown_orange_and_grey(self): + self.assertEqual( + resistor_label(["green", "brown", "orange", "grey"]), "51 kiloohms ±0.05%" + ) + + def test_one_black_band(self): + self.assertEqual(resistor_label(["black"]), "0 ohms") + + def test_orange_orange_yellow_black_and_brown(self): + self.assertEqual( + resistor_label(["orange", "orange", "yellow", "black", "brown"]), "334 ohms ±1%" + ) + + def test_red_green_yellow_yellow_and_brown(self): + self.assertEqual( + resistor_label(["red", "green", "yellow", "yellow", "brown"]), "2.54 megaohms ±1%" + ) + + def test_blue_grey_white_red_and_brown(self): + self.assertEqual( + resistor_label(["blue", "grey", "white", "brown", "brown"]), "6.89 kiloohms ±1%" + ) + + def test_violet_orange_red_and_grey(self): + self.assertEqual( + resistor_label(["violet", "orange", "red", "grey"]), "7.3 kiloohms ±0.05%" + ) + + def test_brown_red_orange_green_and_blue(self): + self.assertEqual( + resistor_label(["brown", "red", "orange", "green", "blue"]), "12.3 megaohms ±0.25%" + ) diff --git a/exercises/practice/resistor-color-master/.docs/instructions.md b/exercises/practice/resistor-color-master/.docs/instructions.md deleted file mode 100644 index 8263897d07..0000000000 --- a/exercises/practice/resistor-color-master/.docs/instructions.md +++ /dev/null @@ -1,86 +0,0 @@ -# Description - -If you want to build something using a Raspberry Pi, you'll probably use _resistors_. -For this exercise, you need to be able to use 1, 4, and 5 bands resistor: - -- Each resistor has a resistance value. -- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. - To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. -- Each band acts as a digit of a number. - For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. - In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. - The program will take 1, 4, or 5 colors as input, and outputs the correct value, in ohms. - The color bands are encoded as follows: - -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 - -In `resistor-color trio` you decoded the first three colors. -For instance: orange-orange-brown got the main value `330`. -In this exercise you need to add tolerance to the mix. -Tolerance is the maximum amount that the value can be above or below the main value. -The tolerance value is always the last band. -For example, if the last band is green, the maximum tolerance will be 0.5%. -The tolerance band will have one of these values: - -- Grey - 0.05% -- Violet - 0.1% -- Blue - 0.25% -- Green - 0.5% -- Brown - 1% -- Red - 2% -- Gold - 5% -- Silver - 10% - -The fourth band resistor is built up like these: - -| Band_1 | Band_2 | Band_3 | band_4 | -| ------- | ------- | ---------- | --------- | -| Value_1 | Value_2 | Multiplier | Tolerance | - -Meaning - -- orange-orange-brown-green would be 330 ohms with a 0.5% tolerance. -- orange-orange-red would-grey would be 3300 ohms with 0.005 tolerance. - -The difference between the 4 and 5 band resistor is that there is an extra band for value a more precises value. - -| Band_1 | Band_2 | Band_3 | Band_4 | band_5 | -| ------- | ------- | ------- | ---------- | --------- | -| Value_1 | Value_2 | Value_3 | Multiplier | Tolerance | - -Meaning - -- orange-orange-orange-black-green would be 330 ohms with a 0.5% tolerance. - -There will also be a one band resistor. -This resistor will only have the color black and have the value 0. - -This exercise is about translating the colors into a label: - -> "... ohms ...%" - -So an input of `"orange", "orange", "black, green"` should return: - -> "33 ohms 0.5%" - -When we get more than a thousand ohms, we say "kiloohms". -That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. - -So an input of `"orange", "orange", "orange", grey` should return: - -> "33 kiloohms 0.05%" - -When we get more than a million ohms, we say "megaohms". - -So an input of `"orange", "orange", "orange", "red"` should return: - -> "33 megaohms 2%" diff --git a/exercises/practice/resistor-color-master/.meta/canonical-data.json b/exercises/practice/resistor-color-master/.meta/canonical-data.json deleted file mode 100644 index b25a6b1aca..0000000000 --- a/exercises/practice/resistor-color-master/.meta/canonical-data.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "exercise": "resistor-color-master", - "cases": [ - { - "uuid": "8c4f9fb6-d477-4250-bc57-b325d2be226f", - "description": "Orange, orange, black, and red", - "property": "label", - "input": { - "colors": ["orange", "orange", "black", "red"] - }, - "expected": { - "value": 33, - "tolerance": 2, - "unit": "ohms" - } - }, - { - "uuid": "d1d4a769-9210-43cc-9a14-6af6ce4c0b00", - "description": "Blue, grey, brown, and violet", - "property": "label", - "input": { - "colors": ["blue", "grey", "brown", "violet"] - }, - "expected": { - "value": 680, - "tolerance": 0.1, - "unit": "ohms" - } - }, - { - "uuid": "6af91bc3-8275-4c38-920a-185d30feb5f3", - "description": "Red, black, red, and green", - "property": "label", - "input": { - "colors": ["red", "black", "red", "green"] - }, - "expected": { - "value": 2, - "tolerance": 0.5, - "unit": "kiloohms" - } - }, - { - "uuid": "9c4630bf-0dda-4212-baca-2f5111530b4d", - "description": "Green, brown, orange, and grey", - "property": "label", - "input": { - "colors": ["green", "brown", "orange", "grey"] - }, - "expected": { - "value": 51, - "tolerance": 0.05, - "unit": "kiloohms" - } - }, - { - "uuid": "93b7d0ec-39fb-49f3-bb88-2dd4b8c293af", - "description": "Yellow, violet, yellow, and blue", - "property": "label", - "input": { - "colors": ["yellow", "violet", "yellow", "blue"] - }, - "expected": { - "value": 470, - "tolerance": 0.25, - "unit": "kiloohms" - } - }, - { - "uuid": "5880ddf1-0dc6-4bd0-b9de-5626117cd2c7", - "description": "One black band", - "property": "label", - "input": { - "colors": ["black"] - }, - "expected": { - "value": 0, - "unit": "ohms" - } - }, - { - "uuid": "a5cfda34-3c02-4bda-b183-726791fb43b2", - "description": "Orange, orange, yellow, black, and brown", - "property": "label", - "input": { - "colors": ["orange", "orange", "yellow", "black", "brown"] - }, - "expected": { - "value": 334, - "tolerance": 1, - "unit": "ohms" - } - }, - { - "uuid": "4f0ad96c-cdab-4c84-95dd-7074e889e001", - "description": "Red, green, yellow, yellow, and brown", - "property": "label", - "input": { - "colors": ["red", "green", "yellow", "yellow", "brown"] - }, - "expected": { - "value": 2.54, - "tolerance": 1, - "unit": "megaohms" - } - }, - { - "uuid": "48c66841-208c-46a7-8992-1e84a2eda9e2", - "description": "Blue, grey, white, brown, and brown", - "property": "label", - "input": { - "colors": ["blue", "grey", "white", "brown", "brown"] - }, - "expected": { - "value": 6.89, - "tolerance": 1, - "unit": "kiloohms" - } - }, - { - "uuid": "4f3aeb0c-fd9d-4cbd-b204-554e61978f73", - "description": "Violet, orange, red, and grey", - "property": "label", - "input": { - "colors": ["violet", "orange", "red", "grey"] - }, - "expected": { - "value": 7.3, - "tolerance": 0.05, - "unit": "kiloohms" - } - }, - { - "uuid": "1ae3a17f-8bc0-449c-9831-fddfd2d91c5d", - "description": "Brown, red, orange, green, and blue", - "property": "label", - "input": { - "colors": ["brown", "red", "orange", "green", "blue"] - }, - "expected": { - "value": 12.3, - "tolerance": 0.25, - "unit": "megaohms" - } - } - ] -} diff --git a/exercises/practice/resistor-color-master/.meta/config.json b/exercises/practice/resistor-color-master/.meta/config.json deleted file mode 100644 index 23a09a2cdd..0000000000 --- a/exercises/practice/resistor-color-master/.meta/config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "authors": [ - "meatball133", - "bethanyg" - ], - "files": { - "solution": [ - "resistor_color_master.py" - ], - "test": [ - "resistor_color_master_test.py" - ], - "example": [ - ".meta/example.py" - ] - }, - "blurb": "Convert color codes, as used on resistors, to a human-readable label.", - "source": "Maud de Vries, Erik Schierboom", - "source_url": "https://github.com/exercism/problem-specifications/issues/1549" -} diff --git a/exercises/practice/resistor-color-master/.meta/template.j2 b/exercises/practice/resistor-color-master/.meta/template.j2 deleted file mode 100644 index b49a7f2fd1..0000000000 --- a/exercises/practice/resistor-color-master/.meta/template.j2 +++ /dev/null @@ -1,19 +0,0 @@ -{%- import "generator_macros.j2" as macros with context -%} -{% macro test_case(case) -%} - {%- set input = case["input"] -%} - def test_{{ case["description"] | to_snake }}(self): - self.assertEqual( - {{ case["property"] | to_snake }}({{ case["input"]["colors"] }}), - {%- if case['expected']['tolerance'] %} - "{{ case['expected']['value']}} {{ case['expected']['unit']}} ±{{case['expected']['tolerance']}}%" - {%- else -%} - "{{ case['expected']['value']}} {{ case['expected']['unit']}}" - {% endif %} - ) -{%- endmacro %} -{{ macros.header()}} - -class {{ exercise | camel_case }}Test(unittest.TestCase): - {% for case in cases -%} - {{ test_case(case) }} - {% endfor %} diff --git a/exercises/practice/resistor-color-master/resistor_color_master.py b/exercises/practice/resistor-color-master/resistor_color_master.py deleted file mode 100644 index 1d36841cf1..0000000000 --- a/exercises/practice/resistor-color-master/resistor_color_master.py +++ /dev/null @@ -1,2 +0,0 @@ -def label(colors): - pass diff --git a/exercises/practice/resistor-color-master/resistor_color_master_test.py b/exercises/practice/resistor-color-master/resistor_color_master_test.py deleted file mode 100644 index 27ac0f82a8..0000000000 --- a/exercises/practice/resistor-color-master/resistor_color_master_test.py +++ /dev/null @@ -1,51 +0,0 @@ -import unittest - -from resistor_color_master import ( - label, -) - -# Tests adapted from `problem-specifications//canonical-data.json` - - -class ResistorColorMasterTest(unittest.TestCase): - def test_orange_orange_black_and_red(self): - self.assertEqual(label(["orange", "orange", "black", "red"]), "33 ohms ±2%") - - def test_blue_grey_brown_and_violet(self): - self.assertEqual(label(["blue", "grey", "brown", "violet"]), "680 ohms ±0.1%") - - def test_red_black_red_and_green(self): - self.assertEqual(label(["red", "black", "red", "green"]), "2 kiloohms ±0.5%") - - def test_green_brown_orange_and_grey(self): - self.assertEqual( - label(["green", "brown", "orange", "grey"]), "51 kiloohms ±0.05%" - ) - - def test_one_black_band(self): - self.assertEqual(label(["black"]), "0 ohms") - - def test_orange_orange_yellow_black_and_brown(self): - self.assertEqual( - label(["orange", "orange", "yellow", "black", "brown"]), "334 ohms ±1%" - ) - - def test_red_green_yellow_yellow_and_brown(self): - self.assertEqual( - label(["red", "green", "yellow", "yellow", "brown"]), "2.54 megaohms ±1%" - ) - - def test_blue_grey_white_red_and_brown(self): - self.assertEqual( - label(["blue", "grey", "white", "brown", "brown"]), "6.89 kiloohms ±1%" - ) - - def test_violet_orange_red_and_grey(self): - self.assertEqual( - label(["violet", "orange", "red", "grey"]), "7.3 kiloohms ±0.05%" - ) - - def test_brown_red_orange_green_and_blue(self): - self.assertEqual( - label(["brown", "red", "orange", "green", "blue"]), "12.3 megaohms ±0.25%" - ) From b0d3055d9794b27b1691ef7ea5163a4f9960906e Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 15 Jun 2023 21:58:36 +0200 Subject: [PATCH 463/826] Update based on feedback --- exercises/practice/resistor-color-expert/.docs/introduction.md | 2 +- exercises/practice/resistor-color-expert/.meta/config.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/exercises/practice/resistor-color-expert/.docs/introduction.md b/exercises/practice/resistor-color-expert/.docs/introduction.md index 1c95695d13..fd9e05efc4 100644 --- a/exercises/practice/resistor-color-expert/.docs/introduction.md +++ b/exercises/practice/resistor-color-expert/.docs/introduction.md @@ -1,7 +1,7 @@ # Introduction If you want to build something using a Raspberry Pi, you'll probably use _resistors_. -For this exercise, you need to be able to use 1, 4, and 5 band resistors: +Like the previous `Resistor Color Duo` and `Resistor Color Trio` exercises, you will be translating resistor color bands to human-readable labels. - Each resistor has a resistance value. - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. diff --git a/exercises/practice/resistor-color-expert/.meta/config.json b/exercises/practice/resistor-color-expert/.meta/config.json index 66d1121b6a..1450661bb0 100644 --- a/exercises/practice/resistor-color-expert/.meta/config.json +++ b/exercises/practice/resistor-color-expert/.meta/config.json @@ -14,5 +14,6 @@ ".meta/example.py" ] }, - "blurb": "Convert color codes, as used on resistors with different amounts of bands, to a human-readable label." + "blurb": "Convert color codes as used on resistors with different bands to a human-readable label.", + "source": "Based on earlier resistor color exercises made by Erik Schierboom and Maud de Vries" } From 65a81612c8639b466019e2be4ef538aff889cede Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 15 Jun 2023 13:05:21 -0700 Subject: [PATCH 464/826] Update exercises/practice/resistor-color-expert/.meta/config.json --- exercises/practice/resistor-color-expert/.meta/config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/resistor-color-expert/.meta/config.json b/exercises/practice/resistor-color-expert/.meta/config.json index 1450661bb0..e2f35368a5 100644 --- a/exercises/practice/resistor-color-expert/.meta/config.json +++ b/exercises/practice/resistor-color-expert/.meta/config.json @@ -16,4 +16,5 @@ }, "blurb": "Convert color codes as used on resistors with different bands to a human-readable label.", "source": "Based on earlier resistor color exercises made by Erik Schierboom and Maud de Vries" + "source_url": "https://github.com/exercism/problem-specifications/issues/1464" } From 89938cce4c8c52cf3d862fca90e26663f7cc4cf8 Mon Sep 17 00:00:00 2001 From: Carl Date: Thu, 15 Jun 2023 22:09:18 +0200 Subject: [PATCH 465/826] Fixed config.json --- exercises/practice/resistor-color-expert/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/resistor-color-expert/.meta/config.json b/exercises/practice/resistor-color-expert/.meta/config.json index e2f35368a5..edf50b4a1e 100644 --- a/exercises/practice/resistor-color-expert/.meta/config.json +++ b/exercises/practice/resistor-color-expert/.meta/config.json @@ -15,6 +15,6 @@ ] }, "blurb": "Convert color codes as used on resistors with different bands to a human-readable label.", - "source": "Based on earlier resistor color exercises made by Erik Schierboom and Maud de Vries" + "source": "Based on earlier resistor color exercises made by Erik Schierboom and Maud de Vries", "source_url": "https://github.com/exercism/problem-specifications/issues/1464" } From 79ca2dc4ab83e1c4e508a5439dce34e2f99d5a1c Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Mon, 12 Jun 2023 09:37:58 +0530 Subject: [PATCH 466/826] author intro + two approaches --- .../practice/sublist/.approaches/config.json | 21 +++++++ .../sublist/.approaches/introduction.md | 58 +++++++++++++++++++ .../.approaches/list-manipulation/content.md | 38 ++++++++++++ .../.approaches/list-manipulation/snippet.txt | 8 +++ .../.approaches/using-strings/content.md | 27 +++++++++ .../.approaches/using-strings/snippet.txt | 8 +++ 6 files changed, 160 insertions(+) create mode 100644 exercises/practice/sublist/.approaches/config.json create mode 100644 exercises/practice/sublist/.approaches/introduction.md create mode 100644 exercises/practice/sublist/.approaches/list-manipulation/content.md create mode 100644 exercises/practice/sublist/.approaches/list-manipulation/snippet.txt create mode 100644 exercises/practice/sublist/.approaches/using-strings/content.md create mode 100644 exercises/practice/sublist/.approaches/using-strings/snippet.txt diff --git a/exercises/practice/sublist/.approaches/config.json b/exercises/practice/sublist/.approaches/config.json new file mode 100644 index 0000000000..1c5eb3838b --- /dev/null +++ b/exercises/practice/sublist/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": ["safwansamsudeen"] + }, + "approaches": [ + { + "uuid": "3593cfe3-5cab-4141-b0a2-329148a66bb6", + "slug": "list-manipulations", + "title": "List manipulation", + "blurb": "Manipulate and check lists to solve the exercise", + "authors": ["safwansamsudeen"] + }, + { + "uuid": "eccd1e1e-6c88-4823-9b25-944eccaa92e7", + "slug": "using-strings", + "title": "Using strings", + "blurb": "Convert the lists to string and use string manipulation to solve the exercise", + "authors": ["safwansamsudeen"] + } + ] +} diff --git a/exercises/practice/sublist/.approaches/introduction.md b/exercises/practice/sublist/.approaches/introduction.md new file mode 100644 index 0000000000..abfcfb70c3 --- /dev/null +++ b/exercises/practice/sublist/.approaches/introduction.md @@ -0,0 +1,58 @@ +# Introduction +There are two broad ways to solve Sublist. + +## General guidance +To write the code, you need to branch out (probably with `if`) into the four different possible conditions, and return the appropriate name of the category. + +## Approach: list manipulation +The direct approach would be to manipulate and check the given lists to solve this. +This solution uses a helper function, which simplifies things, but the approach can be implemented without it. + +```python +SUBLIST = 1 +SUPERLIST = 2 +EQUAL = 3 +UNEQUAL = 4 + +def check_sub_sequences(list_one, list_two): + n1 = len(list_one) + n2 = len(list_two) + return any(list_two[i:i+n1] == list_one for i in range(n2 - n1 + 1)) + +def sublist(list_one, list_two): + if list_one == list_two: + return EQUAL + if check_sub_sequences(list_one, list_two): + return SUBLIST + if check_sub_sequences(list_two, list_one): + return SUPERLIST + return UNEQUAL +``` + +Read more on the [detail of this approach][approach-list-manipulation]. + +## Approach: using strings +Another clever approach is to convert the lists to strings and then use the `in` operator to check for sub-sequences. +Note that this approach is not as performant as the previous one. +```python +SUBLIST = 1 +SUPERLIST = 2 +EQUAL = 3 +UNEQUAL = 4 + +def sublist(list_one, list_two): + list_one_check = (str(list_one).strip("[]") + ",") + list_two_check = (str(list_two).strip("[]") + ",") + + if list_one_check == list_two_check: + return EQUAL + elif list_one_check in list_two_check: + return SUBLIST + elif list_two_check in list_one_check: + return SUPERLIST + return UNEQUAL +``` +To understand more about this approach, [read here][approach-using-strings] + +[approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/list-manipulation +[approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/using-strings \ No newline at end of file diff --git a/exercises/practice/sublist/.approaches/list-manipulation/content.md b/exercises/practice/sublist/.approaches/list-manipulation/content.md new file mode 100644 index 0000000000..ac374b730e --- /dev/null +++ b/exercises/practice/sublist/.approaches/list-manipulation/content.md @@ -0,0 +1,38 @@ +# List manipulation +The direct approach would be to manipulate and check the given lists to solve this. +This solution uses a helper function, which simplifies things, but the approach can be implemented without it. + +```python +SUBLIST = 1 +SUPERLIST = 2 +EQUAL = 3 +UNEQUAL = 4 + +def check_sub_sequences(list_one, list_two): + n1 = len(list_one) + n2 = len(list_two) + return any(list_two[i:i+n1] == list_one for i in range(n2 - n1 + 1)) + +def sublist(list_one, list_two): + if list_one == list_two: + return EQUAL + if check_sub_sequences(list_one, list_two): + return SUBLIST + if check_sub_sequences(list_two, list_one): + return SUPERLIST + return UNEQUAL +``` + +We first check for equality using the `==` operator, if so, then we return `EQUAL`. +A common way to do this differently would be to return `1` directly, but this is better practice as we [remove magic values][magic values]. + +After that we call `check_sub_sequences` passing in `list_one` and `list_two`. +In the helper function, we check if `any` of the possible sub-sequences in `list_two` of length `n1` (the length of the first list) are equal to the first list. +If so, then we conclude that `list_one` is a `SUBLIST` of `list_two`. + +To find whether `list_one` is a `SUPERLIST` of `list_two`, we just reverse this process - pass in the lists in the opposite order. +Thus, we check if `any` of the possible sub-sequences in `list_one` of length `n2` (the length of the second list) are equal to the second list. + +If none of the above conditions are true, we conclude that the two lists are unequal. + +[magic values]: https://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad \ No newline at end of file diff --git a/exercises/practice/sublist/.approaches/list-manipulation/snippet.txt b/exercises/practice/sublist/.approaches/list-manipulation/snippet.txt new file mode 100644 index 0000000000..290f8cdd2b --- /dev/null +++ b/exercises/practice/sublist/.approaches/list-manipulation/snippet.txt @@ -0,0 +1,8 @@ +def sublist(list_one, list_two): + if list_one == list_two: + return EQUAL + if check_sub_sequences(list_one, list_two): + return SUBLIST + if check_sub_sequences(list_two, list_one): + return SUPERLIST + return UNEQUAL \ No newline at end of file diff --git a/exercises/practice/sublist/.approaches/using-strings/content.md b/exercises/practice/sublist/.approaches/using-strings/content.md new file mode 100644 index 0000000000..48ac206fd9 --- /dev/null +++ b/exercises/practice/sublist/.approaches/using-strings/content.md @@ -0,0 +1,27 @@ +# Using strings + +Another clever solution is to convert the lists to strings and then use the `in` operator to check for sub-sequences. +Note that this approach is not as performant as the previous one. +```python +SUBLIST = 1 +SUPERLIST = 2 +EQUAL = 3 +UNEQUAL = 4 + +def sublist(list_one, list_two): + list_one_check = (str(list_one).strip("[]") + ",") + list_two_check = (str(list_two).strip("[]") + ",") + + if list_one_check == list_two_check: + return EQUAL + elif list_one_check in list_two_check: + return SUBLIST + elif list_two_check in list_one_check: + return SUPERLIST + return UNEQUAL +``` +Note that we can't use `.join` as it only accepts strings inside the iterable, while there the test cases have integers. + +In this approach, we convert the lists to strings, so `[1, 2, 3]` becomes `"[1, 2, 3]"`, remove the brackets `"1, 2, 3"`, and add a comma `"1, 2, 3,"` so that there's a consistent pattern of number + comma while using the `in` operator. + +We check equality and then use the `in` operator to check for `SUBLIST` or `SUPERLIST`, and finally return `UNEQUAL`. \ No newline at end of file diff --git a/exercises/practice/sublist/.approaches/using-strings/snippet.txt b/exercises/practice/sublist/.approaches/using-strings/snippet.txt new file mode 100644 index 0000000000..ff7a2563be --- /dev/null +++ b/exercises/practice/sublist/.approaches/using-strings/snippet.txt @@ -0,0 +1,8 @@ +def sublist(list_one, list_two): + list_one_check = (str(list_one).strip("[]") + ",") + list_two_check = (str(list_two).strip("[]") + ",") + ... + elif list_one_check in list_two_check: + return SUBLIST + ... + return UNEQUAL \ No newline at end of file From 71998301caf084dea6121c1f69430bb6a75bf629 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Mon, 12 Jun 2023 09:52:35 +0530 Subject: [PATCH 467/826] minor corrections --- exercises/practice/sublist/.approaches/introduction.md | 2 +- .../practice/sublist/.approaches/using-strings/content.md | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/exercises/practice/sublist/.approaches/introduction.md b/exercises/practice/sublist/.approaches/introduction.md index abfcfb70c3..8cd30870cc 100644 --- a/exercises/practice/sublist/.approaches/introduction.md +++ b/exercises/practice/sublist/.approaches/introduction.md @@ -55,4 +55,4 @@ def sublist(list_one, list_two): To understand more about this approach, [read here][approach-using-strings] [approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/list-manipulation -[approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/using-strings \ No newline at end of file +[approach-using-strings]: https://exercism.org/tracks/python/exercises/sublist/approaches/using-strings \ No newline at end of file diff --git a/exercises/practice/sublist/.approaches/using-strings/content.md b/exercises/practice/sublist/.approaches/using-strings/content.md index 48ac206fd9..f49f59309c 100644 --- a/exercises/practice/sublist/.approaches/using-strings/content.md +++ b/exercises/practice/sublist/.approaches/using-strings/content.md @@ -20,8 +20,11 @@ def sublist(list_one, list_two): return SUPERLIST return UNEQUAL ``` -Note that we can't use `.join` as it only accepts strings inside the iterable, while there the test cases have integers. +Note that we can't use directly `.join` as it only accepts strings inside the iterable, while there the test cases have integers. +However, if one wanted to use it, we could use `map` or a [generator expression][gen-exp] inside `.join`. In this approach, we convert the lists to strings, so `[1, 2, 3]` becomes `"[1, 2, 3]"`, remove the brackets `"1, 2, 3"`, and add a comma `"1, 2, 3,"` so that there's a consistent pattern of number + comma while using the `in` operator. -We check equality and then use the `in` operator to check for `SUBLIST` or `SUPERLIST`, and finally return `UNEQUAL`. \ No newline at end of file +We check equality and then use the `in` operator to check for `SUBLIST` or `SUPERLIST`, and finally return `UNEQUAL`. + +[gen-exp]: https://www.programiz.com/python-programming/generator \ No newline at end of file From 50069bc2f512855b3807a66f82e79e66a9cb556f Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Mon, 12 Jun 2023 09:54:31 +0530 Subject: [PATCH 468/826] correct config errors --- exercises/practice/sublist/.approaches/config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/sublist/.approaches/config.json b/exercises/practice/sublist/.approaches/config.json index 1c5eb3838b..ce54db9c14 100644 --- a/exercises/practice/sublist/.approaches/config.json +++ b/exercises/practice/sublist/.approaches/config.json @@ -4,14 +4,14 @@ }, "approaches": [ { - "uuid": "3593cfe3-5cab-4141-b0a2-329148a66bb6", - "slug": "list-manipulations", + "uuid": "db47397a-4551-49e8-8775-7e7aad79a38b", + "slug": "list-manipulation", "title": "List manipulation", "blurb": "Manipulate and check lists to solve the exercise", "authors": ["safwansamsudeen"] }, { - "uuid": "eccd1e1e-6c88-4823-9b25-944eccaa92e7", + "uuid": "61366160-c859-4d16-9085-171428209b8d", "slug": "using-strings", "title": "Using strings", "blurb": "Convert the lists to string and use string manipulation to solve the exercise", From a260525ea900c47b4d14bbe5600bf6d5428f9ced Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen Date: Thu, 15 Jun 2023 06:45:02 +0530 Subject: [PATCH 469/826] correct approach --- .../sublist/.approaches/introduction.md | 8 +++-- .../.approaches/using-strings/content.md | 33 ++++++++++++++----- .../.approaches/using-strings/snippet.txt | 4 +-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/exercises/practice/sublist/.approaches/introduction.md b/exercises/practice/sublist/.approaches/introduction.md index 8cd30870cc..ac426baec6 100644 --- a/exercises/practice/sublist/.approaches/introduction.md +++ b/exercises/practice/sublist/.approaches/introduction.md @@ -32,8 +32,9 @@ def sublist(list_one, list_two): Read more on the [detail of this approach][approach-list-manipulation]. ## Approach: using strings -Another clever approach is to convert the lists to strings and then use the `in` operator to check for sub-sequences. -Note that this approach is not as performant as the previous one. +Another seemingly clever approach is to convert the lists to strings and then +use the `in` operator to check for sub-sequences. +**However, this does not work.** ```python SUBLIST = 1 SUPERLIST = 2 @@ -52,7 +53,8 @@ def sublist(list_one, list_two): return SUPERLIST return UNEQUAL ``` -To understand more about this approach, [read here][approach-using-strings] +To understand more about this approach and **why it fails**, [read here] +[approach-using-strings]. [approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/list-manipulation [approach-using-strings]: https://exercism.org/tracks/python/exercises/sublist/approaches/using-strings \ No newline at end of file diff --git a/exercises/practice/sublist/.approaches/using-strings/content.md b/exercises/practice/sublist/.approaches/using-strings/content.md index f49f59309c..ff960902dc 100644 --- a/exercises/practice/sublist/.approaches/using-strings/content.md +++ b/exercises/practice/sublist/.approaches/using-strings/content.md @@ -1,7 +1,13 @@ # Using strings +~~~~exercism/caution +**This approach does not work, and this document exists to explain that.** +Please do not use it in your code. +~~~~ -Another clever solution is to convert the lists to strings and then use the `in` operator to check for sub-sequences. -Note that this approach is not as performant as the previous one. +Another seemingly clever solution is to convert the lists to strings and then +use the `in` operator to check for sub-sequences. +Note that this approach, even if it worked, is not as performant as the +previous one. ```python SUBLIST = 1 SUPERLIST = 2 @@ -9,8 +15,8 @@ EQUAL = 3 UNEQUAL = 4 def sublist(list_one, list_two): - list_one_check = (str(list_one).strip("[]") + ",") - list_two_check = (str(list_two).strip("[]") + ",") + list_one_check = str(list_one).strip("[]") + list_two_check = str(list_two).strip("[]") if list_one_check == list_two_check: return EQUAL @@ -20,11 +26,22 @@ def sublist(list_one, list_two): return SUPERLIST return UNEQUAL ``` -Note that we can't use directly `.join` as it only accepts strings inside the iterable, while there the test cases have integers. -However, if one wanted to use it, we could use `map` or a [generator expression][gen-exp] inside `.join`. +Let's parse the code to see what it does. +In this approach, we convert the lists to strings, so `[1, 2, 3]` becomes `"[1, 2, 3]"`, remove the brackets `"1, 2, 3"`, and add a comma `"1, 2, 3,"`. +We check equality and then use the `in` operator to check for `SUBLIST` or `SUPERLIST`, and finally return `UNEQUAL`. -In this approach, we convert the lists to strings, so `[1, 2, 3]` becomes `"[1, 2, 3]"`, remove the brackets `"1, 2, 3"`, and add a comma `"1, 2, 3,"` so that there's a consistent pattern of number + comma while using the `in` operator. +We add a comma because, say, we call `sublist` with `[1, 2]` and `[1, 22]`. `"1, 2" in "1, 22"` evaluates to `True`, so +the **function would wrongly mark it as `SUBLIST`**. -We check equality and then use the `in` operator to check for `SUBLIST` or `SUPERLIST`, and finally return `UNEQUAL`. +This test can be overridden by changing the code like this: +```python +list_one_check = str(list_one).strip("[]") + ',' +list_two_check = str(list_two).strip("[]") + ',' +``` +Yet, the test case (which doesn't exist in the Exercism test suite) `["1", "2"]` and `["5", "'1', '2',", "7"]` would +fail. + +Students can add any arbitrary string into the representation to try to "defeat" this test - `list_one_check = str +(list_one) + TOKEN`. The test suite currently test `TOKEN = ''`, but not others. [gen-exp]: https://www.programiz.com/python-programming/generator \ No newline at end of file diff --git a/exercises/practice/sublist/.approaches/using-strings/snippet.txt b/exercises/practice/sublist/.approaches/using-strings/snippet.txt index ff7a2563be..26fc3ec0ec 100644 --- a/exercises/practice/sublist/.approaches/using-strings/snippet.txt +++ b/exercises/practice/sublist/.approaches/using-strings/snippet.txt @@ -1,6 +1,6 @@ +# Failing approach def sublist(list_one, list_two): - list_one_check = (str(list_one).strip("[]") + ",") - list_two_check = (str(list_two).strip("[]") + ",") + list_one_check = str(list_one).strip("[]") ... elif list_one_check in list_two_check: return SUBLIST From 1cb26c6d68a24d18a78b39243414befb680c0076 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Fri, 16 Jun 2023 16:38:43 +0530 Subject: [PATCH 470/826] formatting issue --- exercises/practice/sublist/.approaches/introduction.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/exercises/practice/sublist/.approaches/introduction.md b/exercises/practice/sublist/.approaches/introduction.md index ac426baec6..42f991ef08 100644 --- a/exercises/practice/sublist/.approaches/introduction.md +++ b/exercises/practice/sublist/.approaches/introduction.md @@ -53,8 +53,7 @@ def sublist(list_one, list_two): return SUPERLIST return UNEQUAL ``` -To understand more about this approach and **why it fails**, [read here] -[approach-using-strings]. +To understand more about this approach and **why it fails**, [read here][approach-using-strings]. [approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/list-manipulation -[approach-using-strings]: https://exercism.org/tracks/python/exercises/sublist/approaches/using-strings \ No newline at end of file +[approach-using-strings]: https://exercism.org/tracks/python/exercises/sublist/approaches/using-strings From f6caa44faa8fb7d0de9a54ddb5c6183e027429c6 Mon Sep 17 00:00:00 2001 From: Andras Nagy <20251272+BNAndras@users.noreply.github.com> Date: Sat, 17 Jun 2023 09:46:55 -0700 Subject: [PATCH 471/826] Remove ResistorColorMaster reference --- .../resistor-color-expert/resistor_color_expert_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py index 8d17a36409..bcf2052c02 100644 --- a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py +++ b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py @@ -7,7 +7,7 @@ # Tests adapted from `problem-specifications//canonical-data.json` -class ResistorColorMasterTest(unittest.TestCase): +class ResistorColorExpertTest(unittest.TestCase): def test_orange_orange_black_and_red(self): self.assertEqual(resistor_label(["orange", "orange", "black", "red"]), "33 ohms ±2%") From bfad7ef55e18c905eab2777eb1022cde4317ba10 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 4 Jul 2023 11:28:49 -0700 Subject: [PATCH 472/826] [Dictionary & Dictionary-Methods]: Updates & Edits + "Mecha Munch Management" Concept Exercise (#3445) * Updates and edits to dict, dict-methods and inventory-management. * Revised the concept for dictionaries and added the concept for dictionary methods. Added new dict-methods exercise "Mecha Munch Management" and related files. * Updates to fix CI errors and typos. * Fixed docstring indentation error in stub file. * Update tests, examplar and slight adjustements of instructions * Update syntax used * Fix spacing * Minor formatting changes * Update about.md * Update test cases * Resolved merge confilts for exercise introduction and re-arranged the task order. * Added exemplar code. * Corrected parameter naming error caught by flake8. * Added test and various other changes * Added hints and made various readability and formatting changes. * Fixed additional errors and refrences. * Removed merge conflict flag from file. * Omitted user from user cart. * Further edits from code review. * One. Final. Edit. For this round. --------- Co-authored-by: meatball Co-authored-by: meatball <69751659+meatball133@users.noreply.github.com> --- concepts/dict-methods/about.md | 388 ++++++++++-------- concepts/dict-methods/introduction.md | 26 +- concepts/dicts/about.md | 286 +++++++++++-- concepts/dicts/introduction.md | 19 +- config.json | 8 + .../.docs/instructions.md | 28 +- .../.docs/introduction.md | 117 ++++-- .../mecha-munch-management/.docs/hints.md | 56 +++ .../.docs/instructions.md | 129 ++++++ .../.docs/introduction.md | 234 +++++++++++ .../mecha-munch-management/.meta/config.json | 21 + .../mecha-munch-management/.meta/design.md | 59 +++ .../mecha-munch-management/.meta/exemplar.py | 79 ++++ .../mecha-munch-management/dict_methods.py | 65 +++ .../dict_methods_test.py | 161 ++++++++ 15 files changed, 1431 insertions(+), 245 deletions(-) create mode 100644 exercises/concept/mecha-munch-management/.docs/hints.md create mode 100644 exercises/concept/mecha-munch-management/.docs/instructions.md create mode 100644 exercises/concept/mecha-munch-management/.docs/introduction.md create mode 100644 exercises/concept/mecha-munch-management/.meta/config.json create mode 100644 exercises/concept/mecha-munch-management/.meta/design.md create mode 100644 exercises/concept/mecha-munch-management/.meta/exemplar.py create mode 100644 exercises/concept/mecha-munch-management/dict_methods.py create mode 100644 exercises/concept/mecha-munch-management/dict_methods_test.py diff --git a/concepts/dict-methods/about.md b/concepts/dict-methods/about.md index e36fc7082e..20625b1c4f 100644 --- a/concepts/dict-methods/about.md +++ b/concepts/dict-methods/about.md @@ -1,74 +1,85 @@ # Dictionary Methods in Python -A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a [hash table or hashmap][hashtable-wikipedia]. -In Python, it's considered a [mapping type][mapping-types-dict]. -`dicts` enable the retrieval of a value in constant time (on average), given the key. +A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. +Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. +As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. -Compared to searching for a value within a list or array (_without knowing the index position_), a dictionary uses significantly more memory, but has very rapid retrieval. -It's especially useful in scenarios where the collection of items is large and must be accessed/updated frequently. +Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). +Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. +Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. ## Dictionary Methods The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries. Some were introduced in the concept for `dicts`. -Here are a few more - along with some techniques for iterating through and manipulating `dicts`. +Here we cover a few more - along with some techniques for iterating through and manipulating dictionaries. -To quickly populate a dictionary with various `keys` and default values, the _class method_ [`dict.fromkeys(iterable, )`][fromkeys] will iterate through the `keys` and create a new `dict`. All `values` will be set to the `default` value provided. +- `dict.setdefault()` for automatically adding keys without error. +- `dict.fromkeys(iterable, )` for creating a new `dict` from any number of iterables. +- `.keys()`, `.values()`, and `.items()` for convenient iterators. +- `sorted(.items())`. for re-ordering entries in a `dict`. +- `dict_one.update()` for updating one `dict` with overlapping values from another `dict`. +- `dict | other_dict` and `dict |= other_dict` for merging or updating two `dict`s via operators. +- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` for reversed views. +- `.popitem()` for removing and returning a `key`, `value` pair. -```python ->>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here') ->>> new_dict -{'Grassy Green': 'fill in hex color here', - 'Purple Mountains Majesty': 'fill in hex color here', - 'Misty Mountain Pink': 'fill in hex color here'} -``` - -`dict.clear()` will removed all `key:value` pairs from the dictionary, leaving it empty and ready for new entries. +### `setdefault()` for Error-Free Insertion -```python ->>> pallette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} ->>> pallette_II.clear() ->>> pallette_II -{} -``` +The dictionary concept previously covered that `.get(key, )` returns an existing `value` or the `default value` if a `key` is not found in a dictionary, thereby avoiding a `KeyError`. +This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for `key` will be present. -`dict.get(key, )` works similarly to `dict[key]` -- but it will return the `default` if the `key` is not in the dictionary. -If no `default` is given, the method will return `None`. +For a similarly "safe" (_without KeyError_) insertion operation, there is the `.setdefault(key, )` method. +`setdefault(key, )` will return the `value` if the `key` is found in the dictionary. +If the key is **not** found, it will _insert_ the (`key`, `default value`) pair and return the `default value` for use. ```python >>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} ->>> palette_I['Factory Stone Purple'] -Traceback (most recent call last): - line 1, in - palette_I['Factory Stone Purple'] +#Looking for the value associated with key "Rock Brown". +#The key does not exist, so it is added with the default +# value, and the value is returned. +>>> palette.setdefault('Rock Brown', '#694605') +'#694605' -KeyError: 'Factory Stone Purple' +#The (key, default value) pair has now been added to the dictionary. +>>> palette_I +{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Rock Brown': '#694605'} +``` ->>> palette_I.get('Factory Stone Purple', 'That color was not found.') -'That color was not found.' +### Use `fromkeys()` to Populate a Dictionary ->>> palette_I.get('Factory Stone Purple', False) -False +To quickly populate a dictionary with various `keys` and default values, the _class method_ [`fromkeys(iterable, )`][fromkeys] will iterate through an iterable of `keys` and create a new `dict`. +All `values` will be set to the `default value` provided: ->>> palette_I.get('Factory Stone Purple') -None +```python +>>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here') + +{'Grassy Green': 'fill in hex color here', + 'Purple Mountains Majesty': 'fill in hex color here', + 'Misty Mountain Pink': 'fill in hex color here'} ``` -`dict.popitem()` removes & returns a single `key:value` pair from the `dict`. -Pairs are returned in Last-in-First-out (LIFO) order. -If the dictionary is empty, calling `.dict.popitem` will raise a `KeyError`. +### Removing and Returning a (key, value) Pair With `.popitem()` + +`.popitem()` removes & returns a single (`key`, `value`) pair from a dictionary. +Pairs are returned in Last-in-First-out (`LIFO`) order. +If the dictionary is empty, calling `popitem()` will raise a `KeyError`: ```python ->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} +>>> palette_I = {'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd'} >>> palette_I.popitem() ('Misty Mountain Pink', '#f9c5bd') + >>> palette_I.popitem() ('Purple Mountains Majesty', '#8076a3') + >>> palette_I.popitem() ('Grassy Green', '#9bc400') ->>> palette_I.popitem() +#All (key, value) pairs have been removed. +>>> palette_I.popitem() Traceback (most recent call last): line 1, in @@ -77,25 +88,37 @@ Traceback (most recent call last): KeyError: 'popitem(): dictionary is empty' ``` -While `dict.clear()` and `dict.popitem()` are _destructive_ actions, the `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views]. -These views can be used for looping over `dict` content without altering it and are _dynamic_ -- when underlying dictionary data changes, the associated view object will reflect the change. +### Iterating Over Entries in a Dictionary + +The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary. +These views can be used for looping over entries without altering them. +They are also _dynamic_ -- when underlying dictionary data changes, the associated view object will reflect the change: ```python ->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} +>>> palette_I = {'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd'} + +#Using .keys() returns a list of keys. >>> palette_I.keys() dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink']) +#Using .values() returns a list of values. >>> palette_I.values() dict_values(['#9bc400', '#8076a3', '#f9c5bd']) +#Using .items() returns a list of (key, value) tuples. >>> palette_I.items() dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', '#8076a3'), ('Misty Mountain Pink', '#f9c5bd')]) +#Views are dynamic. Changing values in the dict +# changes all of the associated views. >>> palette_I['Purple Mountains Majesty'] = (128, 118, 163) +>>> palette_I['Deep Red'] = '#932432' + >>> palette_I.values() -dict_values(['#9bc400', (128, 118, 163), '#f9c5bd']) +dict_values(['#9bc400', (128, 118, 163), '#f9c5bd', '#932432']) ->>> palette_I['Deep Red'] = '#932432' >>> palette_I.keys() dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'Deep Red']) @@ -103,32 +126,68 @@ dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'D dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')]) ``` -`dict_one.update()` can be used to _combine_ two dictionaries. -This method will take the `key:value` pairs of `dict_two` and write them into `dict_one`. +### More on `.keys()`, `.values()`, and `.items()` + +In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_), iteration using `.keys()`, `.values()`, or `.items()`. +In Python 3.8+, views are also _reversible_. +This allows keys, values, or (key, value) pairs to be iterated over in Last-in, First-out (`LIFO`) order by using `reversed(.keys())`, `reversed(.values())`, or `reversed(.items())`: ```python ->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} ->>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} ->>> palette_I.update(palette_II) ->>> palette_I +>>> palette_II = {'Factory Stone Purple': '#7c677f', + 'Green Treeline': '#478559', + 'Purple baseline': '#161748'} +>>> for item in palette_II.items(): +... print(item) ... +('Factory Stone Purple', '#7c677f') +('Green Treeline', '#478559') +('Purple baseline', '#161748') -{'Grassy Green': '#9bc400', - 'Purple Mountains Majesty': '#8076a3', - 'Misty Mountain Pink': '#f9c5bd', - 'Factory Stone Purple': '#7c677f', - 'Green Treeline': '#478559', - 'Purple baseline': '#161748'} +>>> for item in reversed(palette_II.items()): +... print (item) +... +('Purple baseline', '#161748') +('Green Treeline', '#478559') +('Factory Stone Purple', '#7c677f') +``` + +### Combining Dictionaries with `.update()` + +`.update()` can be used to _combine_ two dictionaries. +This method will take the (`key`,`value`) pairs of `` and write them into ``: + +```python +>>> palette_I = {'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd'} +>>> palette_II = {'Factory Stone Purple': '#7c677f', + 'Green Treeline': '#478559', + 'Purple Baseline': '#161748'} + +>>> palette_I.update(palette_II) + +#Note that new items from palette_II are added. +>>> palette_I +{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple Baseline': '#161748'} ``` -Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`. +Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`: ```python ->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', - 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} ->>> palette_III = {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163), +>>> palette_I = {'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd', + 'Factory Stone Purple': '#7c677f', + 'Green Treeline': '#478559', + 'Purple baseline': '#161748'} + +>>> palette_III = {'Grassy Green': (155, 196, 0), + 'Purple Mountains Majesty': (128, 118, 163), 'Misty Mountain Pink': (249, 197, 189)} >>> palette_I.update(palette_III) + +#Overlapping values in palette_I are replaced with +# values from palette_III >>> palette_I {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163), @@ -137,14 +196,21 @@ Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be 'Green Treeline': '#478559', 'Purple baseline': '#161748'} ``` -Python 3.9 introduces a different means of merging `dicts`: the `union` operators. -`dict | other_dict` will create a **new** `dict`, made up of the `key:value` pairs of `dict` and `other_dict`. -When both dictionaries share keys, the `other_dict` values will take precedence. -`dict |= other` will behave similar to `dict.update()`, but in this case, `other` can be either a `dict` or an iterable of `key:value` pairs. +### Merging and Updating Dictionaries Via the Union (`|`) Operators + +Python 3.9 introduces a different means of merging `dicts`: the `union` operators. +`dict_one | dict_two` will create a **new dictionary**, made up of the (`key`, `value`) pairs of `dict_one` and `dict_two`. +When both dictionaries share keys, `dict_two` values take precedence. ```python ->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} ->>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} +>>> palette_I = {'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd'} + +>>> palette_II = {'Factory Stone Purple': '#7c677f', + 'Green Treeline': '#478559', + 'Purple baseline': '#161748'} + >>> new_dict = palette_I | palette_II >>> new_dict ... @@ -154,44 +220,34 @@ When both dictionaries share keys, the `other_dict` values will take precedence. 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} - - >>> palette_III = {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163), 'Misty Mountain Pink': (249, 197, 189)} - >>> new_dict |= palette_III - >>> new_dict - ... - {'Grassy Green': (155, 196, 0), - 'Purple Mountains Majesty': (128, 118, 163), - 'Misty Mountain Pink': (249, 197, 189), - 'Factory Stone Purple': '#7c677f', - 'Green Treeline': '#478559', - 'Purple baseline': '#161748'} ``` -## Tips and Tricks - -As of Python 3.6, `dicts` preserve the order in which items are inserted, allowing ordered iteration using `.items()`. As of Python 3.8, `dict` _views_ are reversible, allowing keys, values or items to be iterated over reverse of insertion order by using `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())`. +`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: ```python ->>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} ->>> for item in palette_II.items(): -... print(item) -... -('Factory Stone Purple', '#7c677f') -('Green Treeline', '#478559') -('Purple baseline', '#161748') - ->>> for item in reversed(palette_II.items()): -... print (item) +>>> palette_III = {'Grassy Green': (155, 196, 0), + 'Purple Mountains Majesty': (128, 118, 163), + 'Misty Mountain Pink': (249, 197, 189)} +>>> new_dict |= palette_III +>>> new_dict ... -('Purple baseline', '#161748') -('Green Treeline', '#478559') -('Factory Stone Purple', '#7c677f') - +{'Grassy Green': (155, 196, 0), +'Purple Mountains Majesty': (128, 118, 163), +'Misty Mountain Pink': (249, 197, 189), +'Factory Stone Purple': '#7c677f', +'Green Treeline': '#478559', +'Purple baseline': '#161748'} ``` -While `dict` does not have a built-in sorting method, it is possible to sort a dictionary _view_ by keys or values using the built-in `sorted()` with `dict.items()`. The sorted view can then be used to create a new, sorted dictionary. Unless a _sort key_ is specified, the default sort is over dictionary keys. +### Sorting a Dictionary + +Dictionaries do not have a built-in sorting method. +However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `.items()`. +The sorted view can then be used to create a new dictionary. +Unless a _sort key_ is specified, the default sort is over dictionary `keys`. ```python +# Default ordering for a dictionary is last in, first out (LIFO). >>> color_palette = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', @@ -200,6 +256,7 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d 'Purple baseline': '#161748'} +# The default sort order for a dictionary uses the keys. >>> sorted_palette = dict(sorted(color_palette.items())) >>> sorted_palette {'Factory Stone Purple': '#7c677f', @@ -208,7 +265,10 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d 'Misty Mountain Pink': '#f9c5bd', 'Purple Mountains Majesty': '#8076a3', 'Purple baseline': '#161748'} - + + +# A sort key can be provided in the form +# of an anonymous function (lambda). >>> value_sorted_palette = dict(sorted(color_palette.items(), key=lambda color: color[1])) >>> value_sorted_palette {'Purple baseline': '#161748', @@ -217,110 +277,96 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d 'Purple Mountains Majesty': '#8076a3', 'Grassy Green': '#9bc400', 'Misty Mountain Pink': '#f9c5bd'} - ``` -Swapping keys and values reliably in a dictionary takes a little more work, but can be accomplished via a loop using `dict.items()`. But if the values stored in the `dict` are not unique, extra checks are required. Both methods assume that `dict` keys and values are _hashable_. - -```python +### Transposing a Dictionaries Keys and Values +Swapping keys and values reliably in a dictionary takes a little work, but can be accomplished via a `loop` using `dict.items()` or in a dictionary comprehension. +Safe swapping assumes that `dict` keys and values are both _hashable_. +```python color_reference = {'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', - 'Purple baseline': '#161748', - 'Pink highlight': '#f95d9b', - 'Bluewater lowlight': '#39a0ca', - 'Bright Red': '#DE354C', - 'Deep Red': '#932432', - 'Pure Purple': '#3C1874', - 'Purple Tinged Grey': '#283747', - 'Cloud': '#F3F3F3'} - ->>> reversed_color_reference = {} + 'Purple baseline': '#161748'} + +# Creating a new dictionary to hold the swapped entries. +>>> swapped_color_reference = {} + +# Iterating through the dictionary, using values as keys. >>> for key, value in color_reference.items(): -... reversed_color_reference[value] = key +... swapped_color_reference[value] = key ->>> reversed_color_reference +>>> swapped_color_reference {'#8076a3': 'Purple Mountains Majesty', '#f9c5bd': 'Misty Mountain Pink', '#7c677f': 'Factory Stone Purple', '#478559': 'Green Treeline', - '#161748': 'Purple baseline', - '#f95d9b': 'Pink highlight', - '#39a0ca': 'Bluewater lowlight', - '#DE354C': 'Bright Red', - '#932432': 'Deep Red', - '#3C1874': 'Pure Purple', - '#283747': 'Purple Tinged Grey', - '#F3F3F3': 'Cloud'} - - ->>> extended_color_reference = {'#8076a3': 'Purple Mountains Majesty',(128, 118, 163): 'Purple Mountains Majesty', - (21, 28, 0, 36): 'Purple Mountains Majesty','#f9c5bd': 'Misty Mountain Pink', - (249, 197, 189): 'Misty Mountain Pink',(0, 21, 24, 2): 'Misty Mountain Pink', - '#7c677f': 'Factory Stone Purple',(124, 103, 127): 'Factory Stone Purple', - (2, 19, 0, 50): 'Factory Stone Purple','#478559': 'Green Treeline', - (71, 133, 89): 'Green Treeline',(47, 0, 33, 48): 'Green Treeline', - '#161748': 'Purple baseline',(22, 23, 72): 'Purple baseline', - (69, 68, 0, 72): 'Purple baseline','#f95d9b': 'Pink highlight', - (249, 93, 155): 'Pink highlight',(0, 63, 38, 2): 'Pink highlight', - '#39a0ca': 'Bluewater lowlight',(57, 160, 202): 'Bluewater lowlight', - (72, 21, 0, 21): 'Bluewater lowlight','#DE354C': 'Bright Red', - (222, 53, 76): 'Bright Red',(0, 76, 66, 13): 'Bright Red', - '#932432': 'Deep Red',(147, 36, 50): 'Deep Red', - (0, 76, 66, 42): 'Deep Red','#3C1874': 'Pure Purple', - (60, 24, 116): 'Pure Purple',(48, 79, 0, 55): 'Pure Purple', - '#283747': 'Purple Tinged Grey',(40, 55, 71): 'Purple Tinged Grey', - (44, 23, 0, 72): 'Purple Tinged Grey','#F3F3F3': 'Cloud', - (243, 243, 243): 'Cloud',(0, 0, 0, 5): 'Cloud'} + '#161748': 'Purple baseline'} + +# A dictionary comprehension can also be used to swap entries. +>>> swapped = {value: key for key, value in + color_reference.items()} +>>> swapped +{'#8076a3': 'Purple Mountains Majesty', + '#f9c5bd': 'Misty Mountain Pink', + '#7c677f': 'Factory Stone Purple', + '#478559': 'Green Treeline', + '#161748': 'Purple baseline'} +``` + +If the values stored in the `dict` are not unique, extra checks become necessary before key and value swapping can happen: + +```python +# Things become more complicated if there are duplicates in +# potential key values.This dict is arranged by hex, RGB, and HSL +# keys, but values repeat. +>>> extended_colors = {'#8076a3': 'Purple Mountains Majesty', + (128, 118, 163): 'Purple Mountains Majesty', + (21, 28, 0, 36): 'Purple Mountains Majesty', + '#f9c5bd': 'Misty Mountain Pink', + (249, 197, 189): 'Misty Mountain Pink', + (0, 21, 24, 2): 'Misty Mountain Pink', + '#7c677f': 'Factory Stone Purple', + (124, 103, 127): 'Factory Stone Purple', + (2, 19, 0, 50): 'Factory Stone Purple', + '#478559': 'Green Treeline', + (71, 133, 89): 'Green Treeline', + (47, 0, 33, 48): 'Green Treeline'} + +# New empty dictionary for holding swapped entries. >>> consolidated_colors = {} + +# Iterating over (key, value) pairs using .items() >>> for key, value in extended_color_reference.items(): -... if value in consolidated_colors: +... if value in consolidated_colors: #Check if key has already been created. ... consolidated_colors[value].append(key) ... else: -... consolidated_colors[value] = [key] +... consolidated_colors[value] = [key] #Create a value list with the former key in it. >>> consolidated_colors {'Purple Mountains Majesty': ['#8076a3', (128, 118, 163), (21, 28, 0, 36)], 'Misty Mountain Pink': ['#f9c5bd', (249, 197, 189), (0, 21, 24, 2)], 'Factory Stone Purple': ['#7c677f', (124, 103, 127), (2, 19, 0, 50)], - 'Green Treeline': ['#478559', (71, 133, 89), (47, 0, 33, 48)], - 'Purple baseline': ['#161748', (22, 23, 72), (69, 68, 0, 72)], - 'Pink highlight': ['#f95d9b', (249, 93, 155), (0, 63, 38, 2)], - 'Bluewater lowlight': ['#39a0ca', (57, 160, 202), (72, 21, 0, 21)], - 'Bright Red': ['#DE354C', (222, 53, 76), (0, 76, 66, 13)], - 'Deep Red': ['#932432', (147, 36, 50), (0, 76, 66, 42)], - 'Pure Purple': ['#3C1874', (60, 24, 116), (48, 79, 0, 55)], - 'Purple Tinged Grey': ['#283747', (40, 55, 71), (44, 23, 0, 72)], - 'Cloud': ['#F3F3F3', (243, 243, 243), (0, 0, 0, 5)]} - + 'Green Treeline': ['#478559', (71, 133, 89), (47, 0, 33, 48)]} ``` For a detailed explanation of dictionaries and methods for working with them, the [official tutorial][dicts-docs] and the [official library reference][mapping-types-dict] are excellent starting places. -For more on sorting, see the [Sorting HOW TO][sorting-howto] in the python docs. - [Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries. -## Extending Dictionaries: The collections module +[Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries. -The [`collections`][collections-docs] module adds more functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`). -A popular `dict`-oriented member of this module is the [`Counter`][counter-dicts], which automatically counts items and returns them a `dict` with the items as keys and their counts as values. -There is also the [`OrderedDict`][ordered-dicts-docs], which has methods specialized for re-arranging the order of a dictionary. -Finally, there is the [`defaultdict`][default-dicts], a subclass of the built-in `dict` module that, based on a factory method, sets a default value if a key is not found when trying to retrieve or assign the value. +For more on sorting, see the [Sorting HOW TO][sorting-howto] in the Python docs. -[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable -[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table -[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict -[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries -[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp -[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys -[collections-docs]: https://docs.python.org/3/library/collections.html -[counter-dicts]: https://docs.python.org/3/library/collections.html#collections.Counter -[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict -[default-dicts]: https://docs.python.org/2/library/collections.html#collections.defaultdict -[dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views +[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. [dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict +[dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views +[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [fi-dict-guide]: https://blog.finxter.com/python-dictionary +[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys +[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table +[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp +[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [sorting-howto]: https://docs.python.org/3/howto/sorting.html +[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable diff --git a/concepts/dict-methods/introduction.md b/concepts/dict-methods/introduction.md index 52868299b9..8a73264384 100644 --- a/concepts/dict-methods/introduction.md +++ b/concepts/dict-methods/introduction.md @@ -1,16 +1,26 @@ # Dictionary Methods in Python -A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a [hash table or hashmap][hashtable-wikipedia]. -In Python, it's considered a [mapping type][mapping-types-dict]. -`dicts` enable the retrieval of a value in constant time (on average), given the key. +A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. +Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. +As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. -Compared to searching for a value within a list or array (_without knowing the index position_), a dictionary uses significantly more memory, but has very rapid retrieval. -It's especially useful in scenarios where the collection of items is large and must be accessed/updated frequently. +Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). +Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. +Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. +The `dict` class in Python provides many useful [methods][dict-methods], some of which are introduced in the concept exercise for dictionaries. -The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries. -Some are introduced in the concept exercise for `dicts`. -This concept tackles a few more - along with some techniques for iterating through and manipulating `dicts`. +This concept tackles a few more: +- `dict.setdefault()` for automatically adding keys when needed. +- `dict.fromkeys(iterable, )` for creating a new `dict` from any number of iterables. +- `.keys()`, `.values()`, and `.items()` for convenient iterators. +- `sorted(.items())`. for re-ordering entries in a `dict`. +- `dict_one.update()` for updating one `dict` with overlapping values from another `dict`. +- `dict | other_dict` and `dict |= other_dict` for merging or updating two `dict`s via operators. +- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` for reversed views. +- `.popitem()` for removing and returning a `key`, `value` pair. + +[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table [term-hashable]: https://docs.python.org/3/glossary.html#term-hashable diff --git a/concepts/dicts/about.md b/concepts/dicts/about.md index fb43067efe..305abc6c82 100644 --- a/concepts/dicts/about.md +++ b/concepts/dicts/about.md @@ -1,55 +1,287 @@ # About +A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. +Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. -A dictionary (`dict`) is a [mapping type][mapping-types-dict] data structure that associates [hashable][term-hashable] `keys` to `values` -- known in other programming languages as a resizable [hash table or hashmap][hashtable-wikipedia]. - `Keys` can include `numbers`, `str`, `tuples` (of _immutable_ values), or `frozensets`, but must be hashable and unique across the dictionary. - `keys` are _immutable_ - once added to a `dict`, they can only be removed, they cannot be updated. - `values` can be of any or multiple data type(s) or structures, including other dictionaries, built-in types, custom types, or even objects like functions or classes. - `values` associated with any `key` are _mutable_, and can be replaced, updated or altered as long as the `key` entry exists. - Dictionaries enable the retrieval of a `value` in (on average) constant O(1) time, given the `key`. - Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more space in memory, but has significantly more rapid retrieval. - Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and/or updated frequently. +`Keys` must be hashable and unique across the dictionary. +Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values). +They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s. +As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. -## Dictionary creation +`values` can be of any data type or structure. + Values can also nest _arbitrarily_, so they can include lists-of-lists, sub-dictionaries, and other custom or compound data structures. -A simple `dict` can be declared using the literal form `{: , : }`: +Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). +Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. +Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. - ```python +## Dictionary Construction +Dictionaries can be created in many different ways: + - Using the [`fromkeys()`][fromkeys] classmethod + - Creating [dictionary comprehensions][dict-comprehensions] + - Merging two dictionaries via unpacking (`**`) + - Merging dictionaries via the `|` (_update_) operator + - Using a loop to iteratively add entries to a previously created empty `dict`. +Below are the two most straightforward methods of dictionary creation. +### The `dict()` Class Constructor + +`dict()` can be used with any iterable of `key`, `value` pairs or with a series of `=` _arguments_: + +```python +#Passing a list of key,value tuples. +>>> wombat = dict([('name', 'Wombat'),('speed', 23), + ('land_animal', True)]) +{'name': 'Wombat', 'speed': 23, 'land_animal': True} + + +#Using key=value arguments. +>>> bear = dict(name="Black Bear", speed=40, land_animal=True) +{'name': 'Black Bear', 'speed': 40, 'land_animal': True} +``` + +The [documentation on `dicts`][dicts-docs] outlines additional variations and options in constructor use. + + +### Dictionary Literals + +A dictionary can also be directly entered as a _dictionary literal_, using curly brackets (`{}`) enclosing `key : value` pairs. +Entries that are enclosed in the `{}` can also appear on separate lines: + +```python +>>> whale = {"name": "Blue Whale", + "speed": 35, + "land_animal": False} +{'name': 'Blue Whale', 'speed': 35, 'land_animal': False} + +>>> wombat = {'name': 'Wombat', + 'speed': 23, + 'land_animal': True, + 'color': 'Brindle'} + +>>> wombat +{'name': 'Wombat', 'speed': 23, 'land_animal': True, 'color': 'Brindle'} +``` + +Dictionaries can be arbitrarily nested: + +```python +animals = { + "Real" : { + "Winged" : { + "Sparrow" : {'name': 'sparrow','speed': 12, 'land_animal': True}, + "Kestrel" : {'name': 'kestrel', 'speed': 15, 'land_animal': True} + }, + "Legged" : { + "Wombat" : {'name': 'Wombat', 'speed': 23, 'land_animal': True}, + "Black Bear": {'name': 'Black Bear', 'speed': 40, 'land_animal': True}, + "Polecat" : {'name': 'Polecat', 'speed': 15, 'land_animal': True} + }, + "Other" : { + "Whale" : {'name': 'Blue Whale', 'speed': 35, 'land_animal': False}, + "Orca" : {'name': 'Orca', 'speed': 45, 'land_animal': False}, + "Snake" : {'name': 'Python', 'speed': 25, 'land_animal': True} + } + }, + + "Imaginary": { + "Winged" : { + "Dragon" : {'name': 'Fire Dragon','speed': 100, 'land_animal': True}, + "Phoenix" : {'name': 'Phoenix', 'speed': 1500, 'land_animal': True} + }, + "Legged" : { + "Sphinx" : {'name': 'Sphinx','speed': 10, 'land_animal': True}, + "Minotaur" : {'name': 'Minotaur', 'speed': 5, 'land_animal': True} + }, + "Other" : {} + } + } +``` + +## Accessing Values in a `dict` + +You can access a `value` in a dictionary using a _key_ in square brackets. +If a key does not exist, a `KeyError` is thrown: + +```python +>>> bear["speed"] +40 + +>>> bear["color"] +Traceback (most recent call last): + File "", line 1, in +KeyError: 'color' +``` + +Accessing an entry via the `get(, )` method can avoid the `KeyError`: + +```python +>>> bear.get("color", 'not found') +'not found' +``` + +### Nested Dictionary Entries + +To access entries in nested dictionaries, use successive brackets. +If a given key is missing, the usual KeyError will be thrown: + +```python +#Using the animals nested dictionary. +>>> animals["Real"]["winged"]["Kestrel"]["speed"] +15 + +>>> animals["Imaginary"]["winged"]["Kestrel"]["speed"] +Traceback (most recent call last): + File "", line 1, in +KeyError: 'Kestrel' +``` + +To avoid the `KeyError`, `.get()` can be used, but the calls to `.get()` must be _chained_: + +```python +#Using the animals nested dictionary. +#Note the use of parenthesis to enable placing the +# .get() calls on separate lines. +>>> (animals.get("Imaginary", {}) + .get("Legged", {}) + .get("Sphinx", {}) + .get("Color", "I have no idea!")) +'I have no idea!' +``` + +## Changing or Adding Dictionary Values + +You can change an entry `value` by assigning to its _key_: + +```python +#Assigning the value "Grizzly Bear" to the name key. +>>> bear["name"] = "Grizzly Bear" +{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True} + +>>> whale["speed"] = 25 +{'name': 'Blue Whale', 'speed': 25, 'land_animal': False} +``` + +New `key`:`value` pairs can be _added_ in the same fashion: + +```python +# Adding an new "color" key with a new "tawney" value. +>>> bear["color"] = 'tawney' +{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True, 'color': 'tawney'} + +>>> whale["blowholes"] = 1 +{'name': 'Blue Whale', 'speed': 25, 'land_animal': False, 'blowholes': 1} +``` + + +## Removing (Pop-ing and del) Dictionary Entries + +You can use the `.pop()` method to delete a dictionary entry. +`.pop()` removes the (`key`, `value`) pair and returns the `value` for use. +Like `.get()`, `.pop()` accepts second argument (_`dict.pop(, )`_) that will be returned if the `key` is not found. +This prevents a `KeyError` being raised: + +```python +#Using .pop() removes both the key and value, returning the value. +>>> bear.pop("name") +'Grizzly Bear' + + +#The "name" key is now removed from the dictionary. +#Attempting .pop() a second time will throw a KeyError. +>>> bear.pop("name") +Traceback (most recent call last): + File "", line 1, in +KeyError: 'name' + + +#Using a default argument with .pop() will +# prevent a KeyError from a missing key. +>>> bear.pop("name", "Unknown") +'Unknown' ``` - The dictionary constructor `dict(=, =)`, but there are many more ways of creating and initializing dictionaries including the use of a _dict comprehension_ or passing additional constructor parameters as illustrated in the [Python docs][mapping-types-dict]. +You can also use the `del` statement to remove a single or multiple entries. +A `KeError` is raised if the entry to be removed is not found in the dictionary: +```python +>>> wombat = {'name': 'Wombat', + 'speed': 23, + 'land_animal': True, + 'color': 'Brindle', + 'talent': 'Singing', + 'size': 'small'} +#Remove a single entry from the dictionary. +>>> del wombat["color"] +>>> wombat +{'name': 'Wombat', 'speed': 23, 'land_animal': True, 'talent': 'Singing', 'size': 'small'} -Inserting a new `key`:`value` pair can be done with `dict[key] = value` and the value can be retrieved by using `retrieved_value = dict[key]`. -## Methods +#Remove multiple entries from the dictionary. +>>> del wombat["talent"], wombat["size"] +>>> wombat +{'name': 'Wombat', 'speed': 23, 'land_animal': True} -`dicts` implement various methods to allow easy initialization, updating and viewing. -Some useful `dict` methods: +#Attempting a deletion of a non-existent key raises a KeyError +>>> del wombat["number_of_legs"] +Traceback (most recent call last): + File "", line 1, in +KeyError: 'number_of_legs' +``` + +## Looping through/Iterating over a Dictionary -- Retrieve a value "safely" from a dictionary by using the `.get(key, [default])` method. `.get(key, [default])` returns the value for the key **or** the _default value_ if the key is not found, instead of raising a `KeyError`. This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for key will be present. -- Retrieve a value "safely" or insert a default _value_ if the key is not found using the `.setdefault(key, [default])` method. `setdefault(key, [default])` will insert the default value in the dictionary **only** if the key is not found, then it will retrieve either the **newly inserted** default value if the key was not found or the **unchanged** existing value if the key was found. -- Return various _iterable_ views of your `dict` with `.keys()`, `.values()`, `.items()` (_an iterable of (key, value) `tuples`_). +Looping through a dictionary using `for item in dict` or `while item` will iterate over only the _keys _ by default. +You can access the _values_ within the same loop by using _square brackets_: + +```python +>>> for key in bear: +>>> print((key, bear[key])) #this prints a tuple of (key, value) +('name', 'Black Bear') +('speed', 40) +('land_animal', True) +``` + +You can also use the `.items()` method, which returns (`key`, `value`) tuples: + +```python +#dict.items() forms (key, value tuples) that can be +# unpacked and iterated over. +>>> for key, value in whale.items(): +>>> print(key, ":", value) +name : Blue Whale +speed : 25 +land_animal : False +blowholes : 1 +``` + +Likewise, `.keys()` will return the `keys` and `.values()` will return the `values`. For a detailed explanation of dictionaries in Python, the [official documentation][dicts-docs] is an excellent starting place, or you can also check out the [W3-Schools][how-to-dicts] tutorial. -## Extending Dictionaries: The collections module -The [`collections`][collections-docs] module adds more functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`). A popular `dict`-oriented member of this module is the [`Counter`][counter-dicts], which automatically counts items and returns them a `dict` with the items as keys and their counts as values. There is also the [`OrderedDict`][ordered-dicts-docs], which has methods specialized for re-arranging the order of a dictionary. Finally, there is the [`defaultdict`][default-dicts], a subclass of the built-in `dict` module that, based on a factory method, sets a default value if a key is not found when trying to retrieve or assign the value. +## Extending Dictionary Functionality: The Collections Module -[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable -[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table -[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict -[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries -[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp +The [`collections`][collections-docs] module adds specialized functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`). +A popular `dict`-oriented member of this module is the [`Counter`][counter-dicts], which automatically counts items and returns them in a `dict` with the items as keys and their counts as values. +There is also the [`OrderedDict`][ordered-dicts-docs], which has methods specialized for re-arranging the order of a dictionary. +Finally, there is the [`defaultdict`][default-dicts], a subclass of the built-in `dict` module that, based on a factory method, sets a default value if a key is not found when trying to retrieve or assign to a dictionary entry. + +[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. [collections-docs]: https://docs.python.org/3/library/collections.html [counter-dicts]: https://docs.python.org/3/library/collections.html#collections.Counter -[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict [default-dicts]: https://docs.python.org/2/library/collections.html#collections.defaultdict +[dict-comprehensions]: https://www.learnbyexample.org/python-dictionary-comprehension/ +[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[fromkeys]: https://www.w3schools.com/python/ref_dictionary_fromkeys.asp +[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table +[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp +[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict +[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable diff --git a/concepts/dicts/introduction.md b/concepts/dicts/introduction.md index 9a887ccfc6..367a89536f 100644 --- a/concepts/dicts/introduction.md +++ b/concepts/dicts/introduction.md @@ -1,15 +1,22 @@ # Introduction +A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. +Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. -A dictionary (`dict`) is a [mapping type][mapping-types-dict] data structure that associates [hashable][term-hashable] `keys` to `values` -- known in other programming languages as a resizable [hash table or hashmap][hashtable-wikipedia]. - `Keys` can include `numbers`, `str`, `tuples` (of _immutable_ values), or `frozensets`, but must be hashable and unique across the dictionary. - `values` can be of any or multiple data type(s) or structures, including other dictionaries, built-in types, custom types, or even objects like functions or classes. - Dictionaries enable the retrieval of a `value` in (on average) constant O(1) time, given the `key`. - Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more space in memory, but has significantly more rapid retrieval. - Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and/or updated frequently. +`Keys` must be hashable and unique across the dictionary. +Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values). +They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s. +As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. +`values` can be of any data type or structure. + Values can also nest _arbitrarily_, so they can include lists-of-lists, sub-dictionaries, and other custom or compound data structures. +Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). +Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. +Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. + +[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. [hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [term-hashable]: https://docs.python.org/3/glossary.html#term-hashable diff --git a/config.json b/config.json index e8c7f2f505..f386ef39d6 100644 --- a/config.json +++ b/config.json @@ -144,6 +144,14 @@ "prerequisites": ["loops", "lists", "tuples"], "status": "beta" }, + { + "slug": "mecha-munch-management", + "name": "Mecha Munch Management", + "uuid": "5ac0c40c-4038-47b8-945b-8480e4a3f44c", + "concepts": ["dict-methods"], + "prerequisites": ["dicts"], + "status": "wip" + }, { "slug": "locomotive-engineer", "name": "Locomotive Engineer", diff --git a/exercises/concept/inventory-management/.docs/instructions.md b/exercises/concept/inventory-management/.docs/instructions.md index e1c1806c6e..718b91d3e0 100644 --- a/exercises/concept/inventory-management/.docs/instructions.md +++ b/exercises/concept/inventory-management/.docs/instructions.md @@ -4,13 +4,17 @@ In this exercise, you will be managing an inventory system. The inventory should be organized by the item name and it should keep track of the number of items available. -You will have to handle adding items to an inventory. Each time an item appears in a given list, increase the item's quantity by `1` in the inventory. Then, you will have to handle deleting items from an inventory. +You will have to handle adding items to an inventory. +Each time an item appears in a given list, the item's quantity should be increased by `1` in the inventory. +You will also have to handle deleting items from an inventory by decreasing quantities by `1` when requested. + +Finally, you will need to implement a function that will return all the key-value pairs in a given inventory as a `list` of `tuples`. -To finish, you will have to implement a function which returns all the key-value pairs in an inventory as a list of `tuples`. ## 1. Create an inventory based on a list -Implement the `create_inventory()` function that creates an "inventory" from a list of items. It should return a `dict` containing each item name paired with their respective quantity. +Implement the `create_inventory()` function that creates an "inventory" from an input list of items. +It should return a `dict` containing each item name paired with their respective quantity. ```python >>> create_inventory(["coal", "wood", "wood", "diamond", "diamond", "diamond"]) @@ -19,7 +23,7 @@ Implement the `create_inventory()` function that creates an "inventory" from a l ## 2. Add items from a list to an existing dictionary -Implement the `add_items()` function that adds a list of items to an inventory: +Implement the `add_items(, )` function that adds a list of items to the passed-in inventory: ```python >>> add_items({"coal":1}, ["wood", "iron", "coal", "wood"]) @@ -28,23 +32,26 @@ Implement the `add_items()` function that adds a list of items to an inventory: ## 3. Decrement items from the inventory -Implement the `decrement_items()` function that takes a `list` of items. The function should remove one from the available count in the inventory for each time an item appears on the `list`: +Implement the `decrement_items(, )` function that takes a `list` of items. +Your function should remove `1` from an item count for each time that item appears on the `list`: ```python >>> decrement_items({"coal":3, "diamond":1, "iron":5}, ["diamond", "coal", "iron", "iron"]) {"coal":2, "diamond":0, "iron":3} ``` -Item counts in the inventory should not fall below 0. If the number of times an item appears on the list exceeds the count available, the quantity listed for that item should remain at 0 and additional requests for removing counts should be ignored. +Item counts in the inventory should not be allowed to fall below 0. + If the number of times an item appears on the input `list` exceeds the count available, the quantity listed for that item should remain at 0. + Additional requests for removing counts should be ignored once the count falls to zero. ```python >>> decrement_items({"coal":2, "wood":1, "diamond":2}, ["coal", "coal", "wood", "wood", "diamond"]) {"coal":0, "wood":0, "diamond":1} ``` -## 4. Remove an item entirely from the inventory +## 4. Remove an entry entirely from the inventory -Implement the `remove_item(, )` function that removes an item and its count entirely from an inventory: +Implement the `remove_item(, )` function that removes an item and its count entirely from an inventory: ```python >>> remove_item({"coal":2, "wood":1, "diamond":2}, "coal") @@ -58,9 +65,10 @@ If the item is not found in the inventory, the function should return the origin {"coal":2, "wood":1, "diamond":2} ``` -## 5. Return the inventory content +## 5. Return the entire content of the inventory -Implement the `list_inventory()` function that takes an inventory and returns a list of `(item, quantity)` tuples. The list should only include the available items (with a quantity greater than zero): +Implement the `list_inventory()` function that takes an inventory and returns a list of `(item, quantity)` tuples. +The list should only include the _available_ items (_with a quantity greater than zero_): ```python >>> list_inventory({"coal":7, "wood":11, "diamond":2, "iron":7, "silver":0}) diff --git a/exercises/concept/inventory-management/.docs/introduction.md b/exercises/concept/inventory-management/.docs/introduction.md index 1b4a15dc28..2b9ef011e1 100644 --- a/exercises/concept/inventory-management/.docs/introduction.md +++ b/exercises/concept/inventory-management/.docs/introduction.md @@ -1,50 +1,79 @@ # Introduction -A _**dictionary**_ is Python's primary mapping type that associates a _hashable key_ with a value. The lookup by key is more efficient than searching through an array, but does require more memory. +A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. +Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. -## Dict construction -Dictionaries can be created in various ways. Two simple options are the use the `dict()` class constructor or the dict literal declaration with key-value pairs. +`Keys` must be hashable and unique across the dictionary. +Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values). +They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s. +As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. -### Use the `dict()` constructor +`values` can be of any data type or structure. + Values can also nest _arbitrarily_, so they can include lists-of-lists, sub-dictionaries, and other custom or compound data structures. + +Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). +Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. +Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. + + +## Dictionary Construction + +Dictionaries can be created in many ways. +The two most straightforward are using the `dict()`constructor or declaring a `dict` _literal_. + +### The `dict()` Class Constructor + +`dict()` (_the constructor for the dictionary class_) can be used with any iterable of `key`, `value` pairs or with a series of `=` _arguments_: ```python +#Passing a list of key,value tuples. +>>> wombat = dict([('name', 'Wombat'),('speed', 23),('land_animal', True)]) +{'name': 'Wombat', 'speed': 23, 'land_animal': True} + + +#Using key=value arguments. >>> bear = dict(name="Black Bear", speed=40, land_animal=True) {'name': 'Black Bear', 'speed': 40, 'land_animal': True} ``` -### Declare a _dict_ literal +### Dictionary Literals + +A `dict` can also be directly entered as a _dictionary literal_, using curly brackets (`{}`) enclosing `key : value` pairs: ```python >>> whale = {"name": "Blue Whale", "speed": 35, "land_animal": False} {'name': 'Blue Whale', 'speed': 35, 'land_animal': False} ``` -With the dict literal declaration keep in mind that _keys_ are of _data types_ `str` and the colon `:` is used instead of an equal sign `=`. +## Accessing Values in a Dictionary -## Accessing values - -You can access an item in a dictionary using the _key_ of the value. - -### Using _square brackets_ after the dict object +You can access an entry in a dictionary using a _key_ in square (`[]`) brackets. +If a `key` does not exist n the `dict`, a `KeyError` is thrown: ```python >>> bear["speed"] 40 + +>>> bear["color"] +Traceback (most recent call last): + File "", line 1, in +KeyError: 'color' ``` -### Using `.get()` +Accessing an entry via the `.get(, )` method can avoid the `KeyError`: ```python ->>> whale.get("name") -'Blue Whale' +>>> bear.get("color", 'not found') +'not found' ``` -## Changing values +## Changing or Adding Dictionary Values -You can easily change a value of an item using its _key_. +You can change an entry `value` by assigning to its _key_: ```python +#Assigning the value "Grizzly Bear" to the name key. >>> bear["name"] = "Grizzly Bear" {'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True} @@ -52,29 +81,71 @@ You can easily change a value of an item using its _key_. {'name': 'Blue Whale', 'speed': 25, 'land_animal': False} ``` -## Deleting values using keys +New `key`:`value` pairs can be _added_ in the same fashion: + +```python +# Adding an new "color" key with a new "tawney" value. +>>> bear["color"] = 'tawney' +{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True, 'color': 'tawney'} -You can delete an item from a dictionary using `dict.pop()`. This will remove the (`key`, `value`) pair from the dictionary and return the `value` for use. `dict.pop()` accepts second argument, `default` that is returned if the `key` is not found (`dict.pop(, )`). Otherwise, a `KeyError` will be raised for any `key` that is missing. +>>> whale["blowholes"] = 1 +{'name': 'Blue Whale', 'speed': 25, 'land_animal': False, 'blowholes': 1} +``` + +## Removing (Pop-ing) Dictionary Entries + +You can use the `.pop()` method to delete a dictionary entry. +`.pop()` removes the (`key`, `value`) pair and returns the `value` for use. +Like `.get()`, `.pop()` accepts second argument (_`dict.pop(, )`_) that will be returned if the `key` is not found. +This prevents a `KeyError` being raised: ```python +#Using .pop() removes both the key and value, returning the value. >>> bear.pop("name") 'Grizzly Bear' ->>> bear.pop("name", "Unknown") -'Unknown' + + +#The "name" key is now removed from the dictionary. +#Attempting .pop() a second time will throw a KeyError. >>> bear.pop("name") Traceback (most recent call last): File "", line 1, in KeyError: 'name' + + +#Using a default argument with .pop() will prevent a KeyError from a missing key. +>>> bear.pop("name", "Unknown") +'Unknown' ``` -## Looping through a dictionary +## Looping through/Iterating over a Dictionary -Looping through a dictionary using `for item in dict` will iterate over the _keys_, but you can access the _values_ by using _square brackets_. +Looping through a dictionary using `for item in dict` or `while item` will iterate over only the _keys _ by default. +You can access the _values_ within the same loop by using _square brackets_: ```python >>> for key in bear: ->>> (key, bear[key]) +>>> print((key, bear[key])) #this forms a tuple of (key, value) and prints it. ('name', 'Black Bear') ('speed', 40) ('land_animal', True) ``` + +You can also use the `.items()` method, which returns (`key`, `value`) tuples automatically: + +```python +#dict.items() forms (key, value tuples) that can be unpacked and iterated over. +>>> for key, value in whale.items(): +>>> print(key, ":", value) +name : Blue Whale +speed : 25 +land_animal : False +blowholes : 1 +``` + +Likewise, the `.keys()` method will return `keys` and the `.values()` method will return the `values`. + +[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. +[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table +[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable diff --git a/exercises/concept/mecha-munch-management/.docs/hints.md b/exercises/concept/mecha-munch-management/.docs/hints.md new file mode 100644 index 0000000000..9edd70abfe --- /dev/null +++ b/exercises/concept/mecha-munch-management/.docs/hints.md @@ -0,0 +1,56 @@ +# Hints + +## General + +Remember, this is an [MVP][mvp]. +That means you don't need to get too fancy with error handling or different "edge case" scenarios. +It's OK to be simple and direct with the functions you are writing. + +The dictionary section of the [official tutorial][dicts-docs] and the mapping type [official library reference][mapping-types-dict] are excellent places to look for more help with all these methods. + + +## 1. Add Item(s) to the Users Shopping Cart + +- You will need to iterate through each item in `items_to_add`. +- You can avoid a `KeyError` when a key is missing by using a `dict` [method][set-default] that takes a _default value_ as one of its arguments. +- It is also possible to accomplish the same thing manually in the `loop` by using some checking and error handling, but the `dict` method is easier. + +## 2. Read in Items Listed in the Users Notes App + +- Remember, Python's got a method for _everything_. This one is a _classmethod_ that's an easy way to [populate a `dict`][fromkeys] with keys. +- This `dict` method returns a _new dictionary_, populated with default values. If no value is given, the default value will become `None` + +## 3. Update Recipe "Ideas" Section + +- Don't overthink this one! This can be solved in **one** `dict` method call. +- The key word here is .... [_update_][update]. + +## 4. Sort the Items in the User Cart + +- What method would you call to get an [iterable view of items][items] in the dictionary? +- If you had a `list` or a `tuple`, what [`built-in`][builtins] function might you use to sort them? +- The built-in function you want is the one that returns a _copy_, and doesn't mutate the original. + +## 5. Send User Shopping Cart to Store for Fulfillment + +- Having a fresh, empty dictionary here as the `fulfillment_cart` might be handy for adding in items. +- `Looping` through the members of the cart might be the most direct way of accessing things here. +- What method would you call to get an [iterable view of just the keys][keys] of the dictionary? +- Remember that you can get the `value` of a given key by using `[]` syntax. +- If you had a `list` or a `tuple`, what [`built-in`][builtins] function might you use to sort them? +- Remember that the `built-in` function can take an optional `reversed=true` argument. + +## 6. Update the Store Inventory to Reflect what a User Has Ordered. + +- There is a method that will give you an iterable view of (`key`, `value`) pairs from the dictionary. +- You can access an item in a _nested tuple_ using _bracket notation_: `[][]` +- Don't forget to check if an inventory count falls to zero, you'll need to add in the "Out of Stock" message. + +[builtins]: https://docs.python.org/3/library/functions.html +[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys +[items]: https://docs.python.org/3/library/stdtypes.html#dict.items +[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[mvp]: https://en.wikipedia.org/wiki/Minimum_viable_product +[set-default]: https://docs.python.org/3/library/stdtypes.html#dict.setdefault +[update]: https://docs.python.org/3/library/stdtypes.html#dict.update diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md new file mode 100644 index 0000000000..f5fa6f7901 --- /dev/null +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -0,0 +1,129 @@ +# Instructions + +Mecha Munch™, a grocery shopping automation company has just hired you to work on their ordering app. +Your team is tasked with building an MVP (_[minimum viable product][mvp]_) that manages all the basic shopping cart activities, allowing users to add, remove, and sort their grocery orders. +Thankfully, a different team is handling all the money and check-out functions! + +## 1. Add Item(s) to the Users Shopping Cart + +The MVP should allow the user to add items to their shopping cart. +This could be a single item or multiple items at once. +Since this is an MVP, item quantity is indicated by _repeats_. +If a user wants to add 2 Oranges, 'Oranges' will appear twice in the input iterable. +If the user already has the item in their cart, the cart quantity should be increased by 1. +If the item is _new_ to the cart, it should be added with a quantity of 1. + +Create the function `add_items(, )` that takes a cart dictionary and any list-like iterable of items to add as arguments. +It should return a new/updated shopping cart dictionary for the user. + +```python +>>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, + ('Apple', 'Apple', 'Orange', 'Apple', 'Banana')) +{'Banana': 4, 'Apple': 5, 'Orange': 2} + +>>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, + ['Banana', 'Orange', 'Blueberries', 'Banana']) +{'Banana': 5, 'Apple': 2, 'Orange': 2, 'Blueberries': 1} +``` + +## 2. Read in Items Listed in the Users Notes App + +Uh-oh. +Looks like the product team is engaging in [feature creep][feature creep]. +They want to add extra functionality to the MVP. +The application now has to create a shopping cart by reading items off a users notes app. +Convenient for the users, but slightly more work for the team. + +Create the function `read_notes()` that can take any list-like iterable as an argument. +The function should parse the items and create a user shopping cart/dictionary. +Each item should be added with a quantity of 1. +The new user cart should then be returned. + +```python +>>> read_notes(('Banana','Apple', 'Orange')) +{'Banana': 1, 'Apple': 1, 'Orange': 1} + +>>> read_notes(['Blueberries', 'Pear', 'Orange', 'Banana', 'Apple']) +{'Blueberries' : 1, 'Pear' : 1, 'Orange' : 1, 'Banana' : 1, 'Apple' : 1} +``` + +## 3. Update Recipe "Ideas" Section + +The app has an "ideas" section that's filled with finished recipes from various cuisines. +The user can select any one of these recipes and have all its ingredients added to their shopping cart automatically. +The project manager has asked you create a way to edit these "ideas" recipes, since the content team keeps changing around ingredients and quantities. + +Create the function `update_recipes(, )` that takes an "ideas" dictionary and an iterable of recipe updates as arguments. +The function should return the new/updated "ideas" dictionary. + +```python +>>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, +(('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)) +... + +{'Banana Bread' : {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, + 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}} + +>>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}, + 'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}}, +[('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}), +('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}), +('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3})]) +... + +{'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}, + 'Pasta Primavera': {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}, + 'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}} +``` + +## 4. Sort the Items in the User Cart + +Once a user has started a cart, the app allows them to sort their items alphabetically. +This makes things easier to find, and helps when there are data-entry errors like having 'potatoes' and 'Potato' in the database. + +Create the function `sort_entries()` that takes a shopping cart/dictionary as an argument and returns a new, alphabetically sorted one. + +```python +>>> sort_entries({'Banana': 3, 'Apple': 2, 'Orange': 1}) +{'Apple': 2, 'Banana':3, 'Orange': 1} +``` + +## 5. Send User Shopping Cart to Store for Fulfillment + +The app needs to send a given users cart to the store for fulfillment. +However, the shoppers in the store need to know which store isle the item can be found in and if the item needs refrigeration. +So (_rather arbitrarily_) the "fulfillment cart" needs to be sorted in reverse alphabetical order with item quantities combined with location and refrigeration information. + +Create the function `send_to_store(, )` that takes a user shopping cart and a dictionary that has store isle number and a `True`/`False` for refrigeration needed for each item. +The function should `return` a combined "fulfillment cart" that has (quantity, isle, and refrigeration) for each item the customer is ordering. +Items should appear in _reverse_ alphabetical order. + +```python +>>> send_to_store({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, + {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}) +{'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]} +``` + +## 6. Update the Store Inventory to Reflect what a User Has Ordered. + +The app can't just place customer orders endlessly. +Eventually, the store is going to run out of various products. +So your app MVP needs to update the store inventory every time a user sends their order to the store. +Otherwise, customers will order products that aren't actually available. + +Create the function `update_store_inventory(, )` that takes a "fulfillment cart" and a store inventory. +The function should reduce the store inventory amounts by the number "ordered" in the "fulfillment cart" and then return the updated store inventory. +Where a store item count falls to 0, the count should be replaced by the message 'Out of Stock'. + +```python +>>> update_store_inventory({'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, +{'Banana': [15, 'Isle 5', False], 'Apple': [12, 'Isle 4', False], 'Orange': [1, 'Isle 4', False], 'Milk': [4, 'Isle 2', True]}) + +{'Banana': [12, 'Isle 5', False], 'Apple': [10, 'Isle 4', False], 'Orange': ['Out of Stock', 'Isle 4', False], 'Milk': [2, 'Isle 2', True]} +``` + +[feature creep]: https://en.wikipedia.org/wiki/Feature_creep +[mvp]: https://en.wikipedia.org/wiki/Minimum_viable_product diff --git a/exercises/concept/mecha-munch-management/.docs/introduction.md b/exercises/concept/mecha-munch-management/.docs/introduction.md new file mode 100644 index 0000000000..7088a08d98 --- /dev/null +++ b/exercises/concept/mecha-munch-management/.docs/introduction.md @@ -0,0 +1,234 @@ +# Dictionary Methods in Python + +A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. +Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. +As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. + + +Given the `key`, dictionaries enable the retrieval of a `value` in (on average) constant time (_independent of the number of entries_). +Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. +Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. + +## Dictionary Methods + +The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries. +Some were introduced in the concept for `dicts`. +Here we cover a few more - along with some techniques for iterating through and manipulating dictionaries. + +### `setdefault()` for Error-Free Insertion + +The dictionary concept previously covered that `.get(key, )` returns an existing `value` or the `default value` if a `key` is not found in a dictionary, thereby avoiding a `KeyError`. +This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for `key` will be present. + +For a similarly "safe" (_without KeyError_) insertion operation, there is the `.setdefault(key, )` method. +`setdefault(key, )` will return the `value` if the `key` is found in the dictionary. +If the key is **not** found, it will _insert_ the (`key`, `default value`) pair and return the `default value` for use. + +```python +>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} + +#Looking for the value associated with key "Rock Brown". +#The key does not exist, so it is added with the default value, and the value is returned. +>>> palette.setdefault('Rock Brown', '#694605') +'#694605' + +#The (key, default value) pair has now been added to the dictionary. +>>> palette_I +{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Rock Brown': '#694605'} +``` + +### Use `fromkeys()` to Populate a Dictionary + +To quickly populate a dictionary with various `keys` and default values, the _class method_ [`fromkeys(iterable, )`][fromkeys] will iterate through an iterable of `keys` and create a new `dict`. +All `values` will be set to the `default value` provided: + +```python +>>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here') + +{'Grassy Green': 'fill in hex color here', + 'Purple Mountains Majesty': 'fill in hex color here', + 'Misty Mountain Pink': 'fill in hex color here'} +``` + +### Iterating Over Entries in a Dictionary + +The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary. + +These views can be used for looping over entries without altering them. +They are also _dynamic_ -- when underlying dictionary data changes, the associated view object will reflect the change: + +```python +>>> palette_I = {'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd'} + +#Using .keys() returns a list of keys. +>>> palette_I.keys() +dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink']) + +#Using .values() returns a list of values. +>>> palette_I.values() +dict_values(['#9bc400', '#8076a3', '#f9c5bd']) + +#Using .items() returns a list of (key, value) tuples. +>>> palette_I.items() +dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', '#8076a3'), ('Misty Mountain Pink', '#f9c5bd')]) + +#Views are dynamic. Changing values in the dict changes all of the associated views. +>>> palette_I['Purple Mountains Majesty'] = (128, 118, 163) +>>> palette_I['Deep Red'] = '#932432' + +>>> palette_I.values() +dict_values(['#9bc400', (128, 118, 163), '#f9c5bd', '#932432']) + +>>> palette_I.keys() +dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'Deep Red']) + +>>> palette_I.items() +dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')]) +``` + +### More on `.keys()`, `.values()`, and `.items()` + +In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_), iteration using `.keys()`, `.values()`, or `.items()`. +In Python 3.8+, views are also _reversible_. +This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last-in, First-out (`LIFO`) order by using `reversed(.keys())`, `reversed(.values())`, or `reversed(.items())`: + +```python +>>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} + +#Iterating in insertion order +>>> for item in palette_II.items(): +... print(item) +... +('Factory Stone Purple', '#7c677f') +('Green Treeline', '#478559') +('Purple baseline', '#161748') + + +#Iterating in the reverse direction. +>>> for item in reversed(palette_II.items()): +... print (item) +... +('Purple baseline', '#161748') +('Green Treeline', '#478559') +('Factory Stone Purple', '#7c677f') +``` + +### Sorting a Dictionary + +Dictionaries do not have a built-in sorting method. +However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `.items()`. +The sorted view can then be used to create a new dictionary. +Like iteration, the default sort is over dictionary `keys`. + +```python +# Default ordering for a dictionary is last in, first out (LIFO). +>>> color_palette = {'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd', + 'Factory Stone Purple': '#7c677f', + 'Green Treeline': '#478559', + 'Purple baseline': '#161748'} + + +# The default sort order for a dictionary uses the keys. +>>> sorted_palette = dict(sorted(color_palette.items())) +>>> sorted_palette +{'Factory Stone Purple': '#7c677f', + 'Grassy Green': '#9bc400', + 'Green Treeline': '#478559', + 'Misty Mountain Pink': '#f9c5bd', + 'Purple Mountains Majesty': '#8076a3', + 'Purple baseline': '#161748'} +``` + +### Combining Dictionaries with `.update()` + +`.update()` can be used to _combine_ two dictionaries. +This method will take the (`key`,`value`) pairs of `` and write them into ``: + +```python +>>> palette_I = {'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd'} +>>> palette_II = {'Factory Stone Purple': '#7c677f', + 'Green Treeline': '#478559', + 'Purple Baseline': '#161748'} + +>>> palette_I.update(palette_II) + +#Note that new items from palette_II are added. +>>> palette_I +{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple Baseline': '#161748'} +``` + +Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`: + +```python +>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', + 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} +>>> palette_III = {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163), + 'Misty Mountain Pink': (249, 197, 189)} +>>> palette_I.update(palette_III) + +#Overlapping values in palette_I are replaced with values from palette_III +>>> palette_I +{'Grassy Green': (155, 196, 0), + 'Purple Mountains Majesty': (128, 118, 163), + 'Misty Mountain Pink': (249, 197, 189), + 'Factory Stone Purple': '#7c677f', + 'Green Treeline': '#478559', 'Purple baseline': '#161748'} +``` + +### Merging and Updating Dictionaries Via the Union (`|`) Operators + +Python 3.9 introduces a different means of merging `dicts`: the `union` operators. +`dict_one | dict_two` will create a **new dictionary**, made up of the (`key`, `value`) pairs of `dict_one` and `dict_two`. +When both dictionaries share keys, `dict_two` values take precedence. + +```python +>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} +>>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} +>>> new_dict = palette_I | palette_II +>>> new_dict +... +{'Grassy Green': '#9bc400', + 'Purple Mountains Majesty': '#8076a3', + 'Misty Mountain Pink': '#f9c5bd', + 'Factory Stone Purple': '#7c677f', + 'Green Treeline': '#478559', + 'Purple baseline': '#161748'} +``` + +`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: + +```python +>>> palette_III = {'Grassy Green': (155, 196, 0), + 'Purple Mountains Majesty': (128, 118, 163), + 'Misty Mountain Pink': (249, 197, 189)} +>>> new_dict |= palette_III +>>> new_dict +... +{'Grassy Green': (155, 196, 0), +'Purple Mountains Majesty': (128, 118, 163), +'Misty Mountain Pink': (249, 197, 189), +'Factory Stone Purple': '#7c677f', +'Green Treeline': '#478559', +'Purple baseline': '#161748'} +``` + +For a detailed explanation of dictionaries and methods for working with them, the [official tutorial][dicts-docs] and the [official library reference][mapping-types-dict] are excellent starting places. + +[Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries. + +[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. +[dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict +[dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views +[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[fi-dict-guide]: https://blog.finxter.com/python-dictionary +[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys +[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table +[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp +[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable diff --git a/exercises/concept/mecha-munch-management/.meta/config.json b/exercises/concept/mecha-munch-management/.meta/config.json new file mode 100644 index 0000000000..f09d0f2953 --- /dev/null +++ b/exercises/concept/mecha-munch-management/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "meatball133", + "BethanyG" + ], + "contributors": [ + ], + "files": { + "solution": [ + "dict_methods.py" + ], + "test": [ + "dict_methods_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "gross-store", + "blurb": "Learn about dictionary methods by building a shopping cart MVP for the Mecha Munch grocery app." +} diff --git a/exercises/concept/mecha-munch-management/.meta/design.md b/exercises/concept/mecha-munch-management/.meta/design.md new file mode 100644 index 0000000000..4c3c52a0d9 --- /dev/null +++ b/exercises/concept/mecha-munch-management/.meta/design.md @@ -0,0 +1,59 @@ +## Learning objectives + +Cover useful `dict` methods and a few techniques for operating on/manipulating `dicts`. + +- `dict.setdefault()` for automatically adding keys when needed. +- `dict.fromkeys(iterable, )` for creating a new `dict` from any number of iterables. +- `dict.keys()`, `dict.values()`, and `dict.items()` for convenient iterators. +- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` for reversed views. +- `sorted()` with `dict.items()`. for re-ordering entries in a `dict`. +- `dict_one.update()` for updating one `dict` with overlapping values from another `dict`. +- `dict | other_dict` and `dict |= other_dict` for merging or updating two `dict`s via operators. +- `dict.popitem()` for removing and returning a key, value pair. + +- Working more with the `dict` views `items()` , `keys()` or `values()`. (e.g, by sorting information using `sorted()` or by swapping `keys` and `values`, etc.) +- Knowing that Dictionaries can be _nested_, _-- e.g._ ' a dictionary of dictionaries'. +- Considerations when `updating()` or using `union` with dictionaries. + +## Out of scope + +Please take a look at the `dicts` concept exercise [design.md file](https://github.com/exercism/python/edit/main/exercises/concept/inventory-management/.meta/design.md) for `dict` features taught thus far. +While those methods can be used for solutions to this exercise, it isn't necessary to cover them again in detail. Additionally, the following is out of scope: + +- Dictionary comprehensions +- Built-in functions as they relate to this data structure (*e.g.* `len()`, or `enumerate()` +- Considerations of Mutability +- `copy()` vs `deepcopy()` +- Memory and performance characteristics. +- Related `collections` module with `Counter()` and `defaultdict()` + +## Concepts + +- `dicts` +- `dict-methods` + +## Prerequisites + +These are the concepts/concept exercises the student needs to complete/understand before solving this concept exercise. + +- `basics` +- `bools` +- `conditionals` +- `comparisons` +- `dicts` +- `lists` +- `loops` +- `numbers` +- `strings` +- `tuples` + + +## Resources to refer to + +- [Python docs: Tutorial - Dictionaries](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) +- [Python docs: Mapping Type `dict`](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict) +- [Real Python: Dicts](https://realpython.com/python-dicts/) +- [Digital Ocean: Understanding dictionaries in python 3](https://www.digitalocean.com/community/tutorials/understanding-dictionaries-in-python-3) +- [Stack Overflow: exchanging keys with values in a `dict` in Python](https://stackoverflow.com/questions/1031851/how-do-i-exchange-keys-with-values-in-a-dictionary) +- [kite: how to sort a dictionary by key in python](https://www.kite.com/python/answers/how-to-sort-a-dictionary-by-key-in-python) +- [medium: 16 Python Dictionary Tips](https://medium.com/python-in-plain-english/16-intermediate-level-python-dictionary-tips-tricks-and-shortcuts-1376859e1adc) _**note:** this is a good resource for ideas and writing this exericse, but is a subscription-based service, so not the best for linking to_ \ No newline at end of file diff --git a/exercises/concept/mecha-munch-management/.meta/exemplar.py b/exercises/concept/mecha-munch-management/.meta/exemplar.py new file mode 100644 index 0000000000..0390944dbb --- /dev/null +++ b/exercises/concept/mecha-munch-management/.meta/exemplar.py @@ -0,0 +1,79 @@ +"""Functions to manage a users shopping cart items.""" + + +def add_item(current_cart, items_to_add): + """Add items to shopping cart. + + :param current_cart: dict - the current shopping cart. + :param items_to_add: iterable - items to add to the cart. + :return: dict - the updated user cart dictionary. + """ + + for item in items_to_add: + current_cart.setdefault(item, 0) + current_cart[item] += 1 + + return current_cart + + +def read_notes(notes): + """Create user cart from an iterable notes entry. + + :param notes: iterable of items to add to cart. + :return: dict - a user shopping cart dictionary. + """ + + return dict.fromkeys(notes, 1) + + +def update_recipes(ideas, recipe_updates): + """Update the recipe ideas dictionary. + + :param ideas: dict - The "recipe ideas" dict. + :param recipe_updates: dict - dictionary with updates for the ideas section. + :return: dict - updated "recipe ideas" dict. + """ + + ideas.update(recipe_updates) + return ideas + + +def sort_entries(cart): + """Sort a users shopping cart in alphabetically order. + + :param cart: dict - a users shopping cart dictionary. + :return: dict - users shopping cart sorted in alphabetical order. + """ + + return dict(sorted(cart.items())) + + +def send_to_store(cart, isle_mapping): + """Combine users order to isle and refrigeration information. + + :param cart: dict - users shopping cart dictionary. + :param isle_mapping: dict - isle and refrigeration information dictionary. + :return: dict - fulfillment dictionary ready to send to store. + """ + fulfillment_cart = {} + + for key in cart.keys(): + fulfillment_cart[key] = [cart[key]] + isle_mapping[key] + + return dict(sorted(fulfillment_cart.items(), reverse=True)) + + +def update_store_inventory(fulfillment_cart, store_inventory): + """Update store inventory levels with user order. + + :param fulfillment cart: dict - fulfillment cart to send to store. + :param store_inventory: dict - store available inventory + :return: dict - store_inventory updated. + """ + + for key, values in fulfillment_cart.items(): + store_inventory[key][0] = store_inventory[key][0] - values[0] + if store_inventory[key][0] == 0: + store_inventory[key][0] = 'Out of Stock' + + return store_inventory diff --git a/exercises/concept/mecha-munch-management/dict_methods.py b/exercises/concept/mecha-munch-management/dict_methods.py new file mode 100644 index 0000000000..d443c8bca5 --- /dev/null +++ b/exercises/concept/mecha-munch-management/dict_methods.py @@ -0,0 +1,65 @@ +"""Functions to manage a users shopping cart items.""" + + +def add_item(current_cart, items_to_add): + """Add items to shopping cart. + + :param current_cart: dict - the current shopping cart. + :param items_to_add: iterable - items to add to the cart. + :return: dict - the updated user cart dictionary. + """ + + pass + + +def read_notes(notes): + """Create user cart from an iterable notes entry. + + :param notes: iterable of items to add to cart. + :return: dict - a user shopping cart dictionary. + """ + + pass + + +def update_recipes(ideas, recipe_updates): + """Update the recipe ideas dictionary. + + :param ideas: dict - The "recipe ideas" dict. + :param recipe_updates: dict - dictionary with updates for the ideas section. + :return: dict - updated "recipe ideas" dict. + """ + + pass + + +def sort_entries(cart): + """Sort a users shopping cart in alphabetically order. + + :param cart: dict - a users shopping cart dictionary. + :return: dict - users shopping cart sorted in alphabetical order. + """ + + pass + + +def send_to_store(cart, isle_mapping): + """Combine users order to isle and refrigeration information. + + :param cart: dict - users shopping cart dictionary. + :param isle_mapping: dict - isle and refrigeration information dictionary. + :return: dict - fulfillment dictionary ready to send to store. + """ + + pass + + +def update_store_inventory(fulfillment_cart, store_inventory): + """Update store inventory levels with user order. + + :param fulfillment cart: dict - fulfillment cart to send to store. + :param store_inventory: dict - store available inventory + :return: dict - store_inventory updated. + """ + + pass diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py new file mode 100644 index 0000000000..2f5828d615 --- /dev/null +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -0,0 +1,161 @@ +import unittest +import pytest +from dict_methods import (add_item, + read_notes, + update_recipes, + sort_entries, + send_to_store, + update_store_inventory) + + +class MechaMunchManagementTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_add_item(self): + input_data = [ + ({'Apple': 1, 'Banana': 4 }, ('Apple', 'Banana', 'Orange')), + ({'Orange': 1, 'Raspberry': 1, 'Blueberries': 10}, ['Raspberry', 'Blueberries', 'Raspberry']), + ({'Broccoli': 1, 'Banana': 1}, ('Broccoli', 'Kiwi', 'Kiwi', 'Kiwi', 'Melon', 'Apple', 'Banana', 'Banana')) + ] + + output_data = [{'Apple': 2, 'Banana': 5, 'Orange': 1}, + {'Orange': 1, 'Raspberry': 3, 'Blueberries': 11}, + {'Broccoli': 2, 'Banana': 3, 'Kiwi': 3, 'Melon': 1, 'Apple': 1}] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different shopping cart.' + self.assertEqual(add_item(input_data[0], input_data[1]), output_data, msg=error_msg) + + + @pytest.mark.task(taskno=2) + def test_read_notes(self): + input_data = [('Apple', "Banana"), ('Orange', 'Raspberry', 'Blueberries'), + ['Broccoli', 'Kiwi', 'Melon', 'Apple', 'Banana']] + + output_data = [{'Apple': 1, 'Banana': 1}, {'Orange': 1, 'Raspberry': 1, 'Blueberries': 1}, + {'Broccoli': 1, 'Kiwi': 1, 'Melon': 1, 'Apple': 1, 'Banana': 1}] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different shopping cart.' + self.assertEqual(read_notes(input_data), output_data, msg=error_msg) + + @pytest.mark.task(taskno=3) + def test_update_recipes(self): + input_data = [ + ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, + (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)), + + ({'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, + 'Blueberry Pie': {'Blueberries': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, + (('Blueberry Pie', {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}), + ('Apple Pie', {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}))), + + ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}, + 'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}}, + (('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}), + ('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}), + ('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}))) + ] + + output_data = [ + {'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, + 'Raspberry Pie': {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, + {'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, + 'Blueberry Pie': {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}}, + {'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}, + 'Pasta Primavera': {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}, + 'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}} + ] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different ideas instead.' + self.assertEqual(update_recipes(input_data[0], input_data[1]), output_data, msg=error_msg) + + @pytest.mark.task(taskno=4) + def test_sort_entries(self): + input_data = [ + {'Banana': 4, 'Apple': 2, 'Orange': 1, 'Pear': 12}, + {'Apple': 3, 'Orange': 5, 'Banana': 1, 'Avocado': 2}, + {'Orange': 3, 'Banana': 2, 'Apple': 1}, + {'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4} + ] + + output_data = [ + {'Apple': 2, 'Banana': 4, 'Orange': 1, 'Pear': 12}, + {'Avocado': 2, 'Apple': 3, 'Banana': 1, 'Orange': 5}, + {'Apple': 1, 'Orange': 3, 'Banana': 2}, + {'Apple' : 2, 'Blueberries': 5, 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4, 'Raspberry': 2} + ] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different sorted list instead.' + self.assertEqual(sort_entries(input_data), output_data, msg=error_msg) + + @pytest.mark.task(taskno=5) + def test_send_to_store(self): + input_data = [ + ({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, + {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}), + + ({'Kiwi': 3, 'Juice': 5, 'Yoghurt': 2, 'Milk': 5}, + {'Kiwi': ['Isle 6', False], 'Juice': ['Isle 5', False], 'Yoghurt': ['Isle 2', True], 'Milk': ['Isle 2', True]}), + + ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4}, + {'Apple': ['Isle 1', False], 'Raspberry': ['Isle 6', False], 'Blueberries': ['Isle 6', False], + 'Broccoli': ['Isle 3', False], 'Kiwi': ['Isle 6', False], 'Melon': ['Isle 6', False]}) + ] + + output_data = [ + {'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, + {'Juice': [5, 'Isle 5', False], 'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True], 'Kiwi': [3, 'Isle 6', False]}, + {'Kiwi': [1, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], 'Apple': [2, 'Isle 1', False], + 'Raspberry': [2, 'Isle 6', False], 'Blueberries': [5, 'Isle 6', False], 'Broccoli': [2, 'Isle 3', False]} + ] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different fulfillment_cart instead.' + self.assertEqual(send_to_store(input_data[0], input_data[1]), output_data, msg=error_msg) + + @pytest.mark.task(taskno=6) + def test_update_store_inventory(self): + input_data = [ + ({'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], + 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, + {'Banana': [15, 'Isle 5', False], 'Apple': [12, 'Isle 4', False], + 'Orange': [1, 'Isle 4', False], 'Milk': [4, 'Isle 2', True]}), + + ({'Kiwi': [3, 'Isle 6', False]},{'Kiwi': [3, 'Isle 6', False], 'Juice': [5, 'Isle 5', False], + 'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True]}), + + ({'Kiwi': [1, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], 'Apple': [2, 'Isle 1', False], + 'Raspberry': [2, 'Isle 6', False], 'Blueberries': [5, 'Isle 6', False], + 'Broccoli': [1, 'Isle 3', False]}, + {'Apple': [2, 'Isle 1', False], 'Raspberry': [5, 'Isle 6', False], + 'Blueberries': [10, 'Isle 6', False], 'Broccoli': [4, 'Isle 3', False], + 'Kiwi': [1, 'Isle 6', False], 'Melon': [8, 'Isle 6', False]}) + ] + + output_data = [ + {'Banana': [12, 'Isle 5', False], 'Apple': [10, 'Isle 4', False], + 'Orange': ['Out of Stock', 'Isle 4', False], 'Milk': [2, 'Isle 2', True]}, + + {'Juice': [5, 'Isle 5', False], 'Yoghurt': [2, 'Isle 2', True], + 'Milk': [5, 'Isle 2', True], 'Kiwi': ["Out of Stock", 'Isle 6', False]}, + + {'Kiwi': ['Out of Stock', 'Isle 6', False], 'Melon': [4, 'Isle 6', False], + 'Apple': ['Out of Stock', 'Isle 1', False], 'Raspberry': [3, 'Isle 6', False], + 'Blueberries': [5, 'Isle 6', False], 'Broccoli': [3, 'Isle 3', False]} + ] + + for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): + error_msg=f'Expected: {output_data} but got a different store inventory instead.' + self.assertEqual(update_store_inventory(input_data[0], input_data[1]), output_data, msg=error_msg) From 4206810a2c6f5ace847592d6307e82577ff2ed88 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 5 Jul 2023 06:15:01 -0700 Subject: [PATCH 473/826] Concept tidy-up and typo fixes. --- concepts/dict-methods/about.md | 79 ++++++++----------- concepts/dict-methods/introduction.md | 27 ++----- concepts/dicts/about.md | 57 +++++++------ concepts/dicts/introduction.md | 2 + .../.docs/introduction.md | 57 ++++++------- 5 files changed, 97 insertions(+), 125 deletions(-) diff --git a/concepts/dict-methods/about.md b/concepts/dict-methods/about.md index 20625b1c4f..d910d3e916 100644 --- a/concepts/dict-methods/about.md +++ b/concepts/dict-methods/about.md @@ -1,29 +1,20 @@ # Dictionary Methods in Python -A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. -Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. -As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. - -Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). -Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. -Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. - -## Dictionary Methods - The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries. Some were introduced in the concept for `dicts`. Here we cover a few more - along with some techniques for iterating through and manipulating dictionaries. -- `dict.setdefault()` for automatically adding keys without error. -- `dict.fromkeys(iterable, )` for creating a new `dict` from any number of iterables. -- `.keys()`, `.values()`, and `.items()` for convenient iterators. -- `sorted(.items())`. for re-ordering entries in a `dict`. -- `dict_one.update()` for updating one `dict` with overlapping values from another `dict`. -- `dict | other_dict` and `dict |= other_dict` for merging or updating two `dict`s via operators. -- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` for reversed views. -- `.popitem()` for removing and returning a `key`, `value` pair. +- `dict.setdefault()` automatically adds keys without throwing a KeyError. +- `dict.fromkeys(iterable, )` creates a new `dict` from any number of iterables. +- `.keys()`, `.values()`, and `.items()` provide convenient iterators. +- `sorted(.items())`. can easily re-order entries in a `dict`. +- `dict_one.update()` updates one `dict` with overlapping values from another `dict`. +- `dict | other_dict` and `dict |= other_dict` merges or updates two `dict`s via operators. +- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` produce _reversed_ views. +- `.popitem()` removes and returns a `key`, `value` pair. -### `setdefault()` for Error-Free Insertion + +## `setdefault()` for Error-Free Insertion The dictionary concept previously covered that `.get(key, )` returns an existing `value` or the `default value` if a `key` is not found in a dictionary, thereby avoiding a `KeyError`. This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for `key` will be present. @@ -35,18 +26,17 @@ If the key is **not** found, it will _insert_ the (`key`, `default value`) pair ```python >>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} -#Looking for the value associated with key "Rock Brown". -#The key does not exist, so it is added with the default -# value, and the value is returned. +# Looking for the value associated with key "Rock Brown".The key does not exist, +# so it is added with the default value, and the value is returned. >>> palette.setdefault('Rock Brown', '#694605') '#694605' -#The (key, default value) pair has now been added to the dictionary. +# The (key, default value) pair has now been added to the dictionary. >>> palette_I {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Rock Brown': '#694605'} ``` -### Use `fromkeys()` to Populate a Dictionary +## `fromkeys()` to Populate a Dictionary from an Iterable To quickly populate a dictionary with various `keys` and default values, the _class method_ [`fromkeys(iterable, )`][fromkeys] will iterate through an iterable of `keys` and create a new `dict`. All `values` will be set to the `default value` provided: @@ -59,7 +49,7 @@ All `values` will be set to the `default value` provided: 'Misty Mountain Pink': 'fill in hex color here'} ``` -### Removing and Returning a (key, value) Pair With `.popitem()` +## Remove and Return a (key, value) Pair With `.popitem()` `.popitem()` removes & returns a single (`key`, `value`) pair from a dictionary. Pairs are returned in Last-in-First-out (`LIFO`) order. @@ -78,7 +68,7 @@ If the dictionary is empty, calling `popitem()` will raise a `KeyError`: >>> palette_I.popitem() ('Grassy Green', '#9bc400') -#All (key, value) pairs have been removed. +# All (key, value) pairs have been removed. >>> palette_I.popitem() Traceback (most recent call last): @@ -88,30 +78,31 @@ Traceback (most recent call last): KeyError: 'popitem(): dictionary is empty' ``` -### Iterating Over Entries in a Dictionary +## Iterating Over Entries in a Dictionary Via Views The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary. -These views can be used for looping over entries without altering them. -They are also _dynamic_ -- when underlying dictionary data changes, the associated view object will reflect the change: + +These views can be used to easily loop over entries without altering them. +Views are also _dynamic_ -- when underlying dictionary data changes, the associated `view object` will reflect the change: ```python >>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} -#Using .keys() returns a list of keys. +# Using .keys() returns a list of keys. >>> palette_I.keys() dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink']) -#Using .values() returns a list of values. +# Using .values() returns a list of values. >>> palette_I.values() dict_values(['#9bc400', '#8076a3', '#f9c5bd']) -#Using .items() returns a list of (key, value) tuples. +# Using .items() returns a list of (key, value) tuples. >>> palette_I.items() dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', '#8076a3'), ('Misty Mountain Pink', '#f9c5bd')]) -#Views are dynamic. Changing values in the dict +# Views are dynamic. Changing values in the dict # changes all of the associated views. >>> palette_I['Purple Mountains Majesty'] = (128, 118, 163) >>> palette_I['Deep Red'] = '#932432' @@ -126,11 +117,12 @@ dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'D dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')]) ``` -### More on `.keys()`, `.values()`, and `.items()` +## More on `.keys()`, `.values()`, and `.items()` + +In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_), iteration when using `.keys()`, `.values()`, or `.items()`. -In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_), iteration using `.keys()`, `.values()`, or `.items()`. In Python 3.8+, views are also _reversible_. -This allows keys, values, or (key, value) pairs to be iterated over in Last-in, First-out (`LIFO`) order by using `reversed(.keys())`, `reversed(.values())`, or `reversed(.items())`: +This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last-in, First-out (`LIFO`) order by using `reversed(.keys())`, `reversed(.values())`, or `reversed(.items())`: ```python >>> palette_II = {'Factory Stone Purple': '#7c677f', @@ -151,7 +143,7 @@ This allows keys, values, or (key, value) pairs to be iterated over in Last-in, ('Factory Stone Purple', '#7c677f') ``` -### Combining Dictionaries with `.update()` +## Combine Dictionaries with `.update()` `.update()` can be used to _combine_ two dictionaries. This method will take the (`key`,`value`) pairs of `` and write them into ``: @@ -166,7 +158,7 @@ This method will take the (`key`,`value`) pairs of `` and write them i >>> palette_I.update(palette_II) -#Note that new items from palette_II are added. +# Note that new items from palette_II are added. >>> palette_I {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple Baseline': '#161748'} ``` @@ -186,7 +178,7 @@ Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be 'Misty Mountain Pink': (249, 197, 189)} >>> palette_I.update(palette_III) -#Overlapping values in palette_I are replaced with +# Overlapping values in palette_I are replaced with # values from palette_III >>> palette_I {'Grassy Green': (155, 196, 0), @@ -196,7 +188,7 @@ Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be 'Green Treeline': '#478559', 'Purple baseline': '#161748'} ``` -### Merging and Updating Dictionaries Via the Union (`|`) Operators +## Merge or Update Dictionaries Via the Union (`|`) Operators Python 3.9 introduces a different means of merging `dicts`: the `union` operators. `dict_one | dict_two` will create a **new dictionary**, made up of the (`key`, `value`) pairs of `dict_one` and `dict_two`. @@ -239,7 +231,7 @@ When both dictionaries share keys, `dict_two` values take precedence. 'Purple baseline': '#161748'} ``` -### Sorting a Dictionary +## Sorting a Dictionary Dictionaries do not have a built-in sorting method. However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `.items()`. @@ -279,7 +271,7 @@ Unless a _sort key_ is specified, the default sort is over dictionary `keys`. 'Misty Mountain Pink': '#f9c5bd'} ``` -### Transposing a Dictionaries Keys and Values +## Transposing a Dictionaries Keys and Values Swapping keys and values reliably in a dictionary takes a little work, but can be accomplished via a `loop` using `dict.items()` or in a dictionary comprehension. Safe swapping assumes that `dict` keys and values are both _hashable_. @@ -359,14 +351,11 @@ For a detailed explanation of dictionaries and methods for working with them, th For more on sorting, see the [Sorting HOW TO][sorting-howto] in the Python docs. -[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. [dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict [dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views [dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [fi-dict-guide]: https://blog.finxter.com/python-dictionary [fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys -[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table [how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [sorting-howto]: https://docs.python.org/3/howto/sorting.html -[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable diff --git a/concepts/dict-methods/introduction.md b/concepts/dict-methods/introduction.md index 8a73264384..c15fbc113d 100644 --- a/concepts/dict-methods/introduction.md +++ b/concepts/dict-methods/introduction.md @@ -1,27 +1,16 @@ # Dictionary Methods in Python -A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. -Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. -As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. - -Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). -Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. -Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. The `dict` class in Python provides many useful [methods][dict-methods], some of which are introduced in the concept exercise for dictionaries. This concept tackles a few more: -- `dict.setdefault()` for automatically adding keys when needed. -- `dict.fromkeys(iterable, )` for creating a new `dict` from any number of iterables. -- `.keys()`, `.values()`, and `.items()` for convenient iterators. -- `sorted(.items())`. for re-ordering entries in a `dict`. -- `dict_one.update()` for updating one `dict` with overlapping values from another `dict`. -- `dict | other_dict` and `dict |= other_dict` for merging or updating two `dict`s via operators. -- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` for reversed views. -- `.popitem()` for removing and returning a `key`, `value` pair. +- `dict.setdefault()` automatically adds keys without throwing a `KeyError`. +- `dict.fromkeys(iterable, )` creates a new `dict` from any number of iterables. +- `.keys()`, `.values()`, and `.items()` provide convenient iterators. +- `sorted(.items())`. can easily re-order entries in a `dict`. +- `dict_one.update()` updates one `dict` with overlapping values from another `dict`. +- `dict | other_dict` and `dict |= other_dict` merges or updates two `dict`s via operators. +- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` produce _reversed_ views. +- `.popitem()` removes and returns a `key`, `value` pair. -[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. -[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict -[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table -[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable [dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict diff --git a/concepts/dicts/about.md b/concepts/dicts/about.md index 305abc6c82..c34160b2ef 100644 --- a/concepts/dicts/about.md +++ b/concepts/dicts/about.md @@ -3,7 +3,6 @@ A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. - `Keys` must be hashable and unique across the dictionary. Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values). They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s. @@ -14,32 +13,34 @@ As of Python 3.7, `dict` key order is guaranteed to be the order in which entrie Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. + Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. ## Dictionary Construction -Dictionaries can be created in many different ways: +Dictionaries can be created in many different ways, including: - Using the [`fromkeys()`][fromkeys] classmethod - Creating [dictionary comprehensions][dict-comprehensions] - Merging two dictionaries via unpacking (`**`) - Merging dictionaries via the `|` (_update_) operator - Using a loop to iteratively add entries to a previously created empty `dict`. -Below are the two most straightforward methods of dictionary creation. +The two most straightforward methods are the dictionary _constructor_ and the dictionary _literal_. -### The `dict()` Class Constructor +### The Dictionary Constructor -`dict()` can be used with any iterable of `key`, `value` pairs or with a series of `=` _arguments_: +`dict()` (_the constructor for the `dict` class_) can be used with any iterable of `key`, `value` pairs. + It can also be called with a series of `=` _arguments_: ```python -#Passing a list of key,value tuples. +# Passing a list of key,value tuples. >>> wombat = dict([('name', 'Wombat'),('speed', 23), ('land_animal', True)]) {'name': 'Wombat', 'speed': 23, 'land_animal': True} -#Using key=value arguments. +# Using key=value arguments. >>> bear = dict(name="Black Bear", speed=40, land_animal=True) {'name': 'Black Bear', 'speed': 40, 'land_animal': True} ``` @@ -67,6 +68,8 @@ Entries that are enclosed in the `{}` can also appear on separate lines: {'name': 'Wombat', 'speed': 23, 'land_animal': True, 'color': 'Brindle'} ``` +### Nested Dictionaries + Dictionaries can be arbitrarily nested: ```python @@ -124,13 +127,13 @@ Accessing an entry via the `get(, )` method can avoid the `K 'not found' ``` -### Nested Dictionary Entries +### Accessing Nested Dictionary Entries To access entries in nested dictionaries, use successive brackets. If a given key is missing, the usual KeyError will be thrown: ```python -#Using the animals nested dictionary. +# Using the animals nested dictionary. >>> animals["Real"]["winged"]["Kestrel"]["speed"] 15 @@ -143,8 +146,8 @@ KeyError: 'Kestrel' To avoid the `KeyError`, `.get()` can be used, but the calls to `.get()` must be _chained_: ```python -#Using the animals nested dictionary. -#Note the use of parenthesis to enable placing the +# Using the animals nested dictionary. +# Note the use of parenthesis to enable placing the # .get() calls on separate lines. >>> (animals.get("Imaginary", {}) .get("Legged", {}) @@ -158,7 +161,7 @@ To avoid the `KeyError`, `.get()` can be used, but the calls to `.get()` must be You can change an entry `value` by assigning to its _key_: ```python -#Assigning the value "Grizzly Bear" to the name key. +# Assigning the value "Grizzly Bear" to the name key. >>> bear["name"] = "Grizzly Bear" {'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True} @@ -186,20 +189,20 @@ Like `.get()`, `.pop()` accepts second argument (_`dict.pop(, >> bear.pop("name") 'Grizzly Bear' -#The "name" key is now removed from the dictionary. -#Attempting .pop() a second time will throw a KeyError. +# The "name" key is now removed from the dictionary. +# Attempting .pop() a second time will throw a KeyError. >>> bear.pop("name") Traceback (most recent call last): File "", line 1, in KeyError: 'name' -#Using a default argument with .pop() will +# Using a default argument with .pop() will # prevent a KeyError from a missing key. >>> bear.pop("name", "Unknown") 'Unknown' @@ -216,29 +219,29 @@ A `KeError` is raised if the entry to be removed is not found in the dictionary: 'talent': 'Singing', 'size': 'small'} -#Remove a single entry from the dictionary. +# Remove a single entry from the dictionary. >>> del wombat["color"] >>> wombat {'name': 'Wombat', 'speed': 23, 'land_animal': True, 'talent': 'Singing', 'size': 'small'} -#Remove multiple entries from the dictionary. +# Remove multiple entries from the dictionary. >>> del wombat["talent"], wombat["size"] >>> wombat {'name': 'Wombat', 'speed': 23, 'land_animal': True} -#Attempting a deletion of a non-existent key raises a KeyError +# Attempting a deletion of a non-existent key raises a KeyError >>> del wombat["number_of_legs"] Traceback (most recent call last): File "", line 1, in KeyError: 'number_of_legs' ``` -## Looping through/Iterating over a Dictionary +## Looping Through/Iterating over a Dictionary -Looping through a dictionary using `for item in dict` or `while item` will iterate over only the _keys _ by default. -You can access the _values_ within the same loop by using _square brackets_: +Looping through a dictionary using `for item in dict` or `while item` will iterate over the _keys_ by default. +You can access _values_ within the same loop by using _square brackets_: ```python >>> for key in bear: @@ -251,7 +254,7 @@ You can access the _values_ within the same loop by using _square brackets_: You can also use the `.items()` method, which returns (`key`, `value`) tuples: ```python -#dict.items() forms (key, value tuples) that can be +# dict.items() forms (key, value tuples) that can be # unpacked and iterated over. >>> for key, value in whale.items(): >>> print(key, ":", value) @@ -269,9 +272,11 @@ For a detailed explanation of dictionaries in Python, the [official documentatio ## Extending Dictionary Functionality: The Collections Module The [`collections`][collections-docs] module adds specialized functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`). -A popular `dict`-oriented member of this module is the [`Counter`][counter-dicts], which automatically counts items and returns them in a `dict` with the items as keys and their counts as values. -There is also the [`OrderedDict`][ordered-dicts-docs], which has methods specialized for re-arranging the order of a dictionary. -Finally, there is the [`defaultdict`][default-dicts], a subclass of the built-in `dict` module that, based on a factory method, sets a default value if a key is not found when trying to retrieve or assign to a dictionary entry. +Three of the most useful dictionary-based classes are: + +- [`Counter`][counter-dicts] automatically counts items and returns them in a `dict` with the items as keys and their counts as values. +- [`OrderedDict`][ordered-dicts-docs], has methods specialized for arranging the order of dictionary entries. +- [`defaultdict`][default-dicts] uses a factory method to set a default value if a `key` is not found when trying to retrieve or assign to a dictionary entry. [associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. [collections-docs]: https://docs.python.org/3/library/collections.html diff --git a/concepts/dicts/introduction.md b/concepts/dicts/introduction.md index 367a89536f..5c8a772480 100644 --- a/concepts/dicts/introduction.md +++ b/concepts/dicts/introduction.md @@ -14,8 +14,10 @@ As of Python 3.7, `dict` key order is guaranteed to be the order in which entrie Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_). Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. + Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. + [associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. [hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict diff --git a/exercises/concept/mecha-munch-management/.docs/introduction.md b/exercises/concept/mecha-munch-management/.docs/introduction.md index 7088a08d98..f1c5744e69 100644 --- a/exercises/concept/mecha-munch-management/.docs/introduction.md +++ b/exercises/concept/mecha-munch-management/.docs/introduction.md @@ -1,21 +1,10 @@ # Dictionary Methods in Python -A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array]. -Dictionaries are Python's only built-in [mapping type][mapping-types-dict]. -As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted. - - -Given the `key`, dictionaries enable the retrieval of a `value` in (on average) constant time (_independent of the number of entries_). -Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval. -Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently. - -## Dictionary Methods - The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries. Some were introduced in the concept for `dicts`. Here we cover a few more - along with some techniques for iterating through and manipulating dictionaries. -### `setdefault()` for Error-Free Insertion +### Use `setdefault()` for Error-Free Insertion The dictionary concept previously covered that `.get(key, )` returns an existing `value` or the `default value` if a `key` is not found in a dictionary, thereby avoiding a `KeyError`. This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for `key` will be present. @@ -27,17 +16,17 @@ If the key is **not** found, it will _insert_ the (`key`, `default value`) pair ```python >>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} -#Looking for the value associated with key "Rock Brown". -#The key does not exist, so it is added with the default value, and the value is returned. +# Looking for the value associated with key "Rock Brown". +# The key does not exist, so it is added with the default value, and the value is returned. >>> palette.setdefault('Rock Brown', '#694605') '#694605' -#The (key, default value) pair has now been added to the dictionary. +# The (key, default value) pair has now been added to the dictionary. >>> palette_I {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Rock Brown': '#694605'} ``` -### Use `fromkeys()` to Populate a Dictionary +## Use `fromkeys()` to Populate a Dictionary from an Iterable To quickly populate a dictionary with various `keys` and default values, the _class method_ [`fromkeys(iterable, )`][fromkeys] will iterate through an iterable of `keys` and create a new `dict`. All `values` will be set to the `default value` provided: @@ -50,31 +39,31 @@ All `values` will be set to the `default value` provided: 'Misty Mountain Pink': 'fill in hex color here'} ``` -### Iterating Over Entries in a Dictionary +## Iterating Over Entries in a Dictionary Via Views The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary. -These views can be used for looping over entries without altering them. -They are also _dynamic_ -- when underlying dictionary data changes, the associated view object will reflect the change: +These views can be used to easily loop over entries without altering them. +Views are also _dynamic_ -- when underlying dictionary data changes, the associated `view object` will reflect the change: ```python >>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'} -#Using .keys() returns a list of keys. +# Using .keys() returns a list of keys. >>> palette_I.keys() dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink']) -#Using .values() returns a list of values. +# Using .values() returns a list of values. >>> palette_I.values() dict_values(['#9bc400', '#8076a3', '#f9c5bd']) -#Using .items() returns a list of (key, value) tuples. +# Using .items() returns a list of (key, value) tuples. >>> palette_I.items() dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', '#8076a3'), ('Misty Mountain Pink', '#f9c5bd')]) -#Views are dynamic. Changing values in the dict changes all of the associated views. +# Views are dynamic. Changing values in the dict changes all of the associated views. >>> palette_I['Purple Mountains Majesty'] = (128, 118, 163) >>> palette_I['Deep Red'] = '#932432' @@ -88,16 +77,17 @@ dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'D dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')]) ``` -### More on `.keys()`, `.values()`, and `.items()` +## More on `.keys()`, `.values()`, and `.items()` + +In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_), iteration when using `.keys()`, `.values()`, or `.items()`. -In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_), iteration using `.keys()`, `.values()`, or `.items()`. In Python 3.8+, views are also _reversible_. This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last-in, First-out (`LIFO`) order by using `reversed(.keys())`, `reversed(.values())`, or `reversed(.items())`: ```python >>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} -#Iterating in insertion order +# Iterating in insertion order >>> for item in palette_II.items(): ... print(item) ... @@ -106,7 +96,7 @@ This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last- ('Purple baseline', '#161748') -#Iterating in the reverse direction. +# Iterating in the reverse direction. >>> for item in reversed(palette_II.items()): ... print (item) ... @@ -115,7 +105,7 @@ This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last- ('Factory Stone Purple', '#7c677f') ``` -### Sorting a Dictionary +## Sorting a Dictionary Dictionaries do not have a built-in sorting method. However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `.items()`. @@ -143,7 +133,7 @@ Like iteration, the default sort is over dictionary `keys`. 'Purple baseline': '#161748'} ``` -### Combining Dictionaries with `.update()` +## Combining Dictionaries with `.update()` `.update()` can be used to _combine_ two dictionaries. This method will take the (`key`,`value`) pairs of `` and write them into ``: @@ -158,7 +148,7 @@ This method will take the (`key`,`value`) pairs of `` and write them i >>> palette_I.update(palette_II) -#Note that new items from palette_II are added. +# Note that new items from palette_II are added. >>> palette_I {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple Baseline': '#161748'} ``` @@ -172,7 +162,7 @@ Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be 'Misty Mountain Pink': (249, 197, 189)} >>> palette_I.update(palette_III) -#Overlapping values in palette_I are replaced with values from palette_III +# Overlapping values in palette_I are replaced with values from palette_III >>> palette_I {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163), @@ -181,7 +171,7 @@ Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be 'Green Treeline': '#478559', 'Purple baseline': '#161748'} ``` -### Merging and Updating Dictionaries Via the Union (`|`) Operators +## Merge or Update Dictionaries Via the Union (`|`) Operators Python 3.9 introduces a different means of merging `dicts`: the `union` operators. `dict_one | dict_two` will create a **new dictionary**, made up of the (`key`, `value`) pairs of `dict_one` and `dict_two`. @@ -222,13 +212,10 @@ For a detailed explanation of dictionaries and methods for working with them, th [Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries. -[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain. [dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict [dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views [dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [fi-dict-guide]: https://blog.finxter.com/python-dictionary [fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys -[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table [how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict -[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable From 7c2f95afff4da6b2a19e2176fbd16f04089861a4 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 5 Jul 2023 07:42:00 -0700 Subject: [PATCH 474/826] Code example fixes for sets and cater-waiter exercise. --- concepts/sets/about.md | 4 ++-- exercises/concept/cater-waiter/.docs/introduction.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index dd567f070f..5f272402ee 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -12,7 +12,7 @@ _Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ord They're also used for fast membership testing, finding supersets & subsets of items, and performing "set math" (_calculating union, intersection, difference & symmetric difference between groups of items._). Checking membership in a `set` has only O(1) time complexity versus checking for membership in a `list` or `string`, which has worst-case O(n) time complexity. -Operations such as `.union()`, `.intersection()`, or `.diference()` have an average O(n) time complexity. +Operations such as `.union()`, `.intersection()`, or `.difference()` have an average O(n) time complexity. ## Construction @@ -52,7 +52,7 @@ set() # The list is unpacked and each distinct element is added. >>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13]) >>> multiple_elements_from_set -{2, 3, 5, 7, 11} +{2, 3, 5, 7, 11, 13} ``` Results when using a set constructor with a string or dictionary may be surprising: diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index fa29f0560e..905504a63b 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -50,7 +50,7 @@ set() # The list is unpacked and each distinct element is added. >>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13]) >>> multiple_elements_from_set -{2, 3, 5, 7, 11} +{2, 3, 5, 7, 11, 13} ``` Sets can hold heterogeneous datatypes, but all `set` elements must be _hashable_: From c79d5a72d8e57a14ee091e6ea43c6aab427ee340 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:43:42 +0200 Subject: [PATCH 475/826] Started --- concepts/string-formatting/about.md | 87 +++++++++++++---------------- 1 file changed, 38 insertions(+), 49 deletions(-) diff --git a/concepts/string-formatting/about.md b/concepts/string-formatting/about.md index 07306a8d8b..46452b4163 100644 --- a/concepts/string-formatting/about.md +++ b/concepts/string-formatting/about.md @@ -2,18 +2,18 @@ ## String Formatting in Python +String formatting is the process of converting values to strings and inserting them into a string template. The [Zen of Python][zen-of-python] asserts there should be "one _obvious_ way to do something in Python". -But when it comes to string formatting, things are a little .... _less zen_. +But when it comes to string formatting, things are a little ... _less zen_. It can be surprising to find out that there are **four** main ways to perform string formatting in Python - each for a different scenario. Some of this is due to Python's long history and some of it is due to considerations like internationalization or input sanitation. We will start with the most recent additions to the string formatting toolbox and work our way backward to "old style" or "printf() style" string formatting. +## literal string interpolation: The `f-string` -## literal string interpolation: The `f-string` - - Introduced in [Python 3.6][pep-0498], [`f-strings`][f-string] (_short for "formatted-strings"_) or [literal string interpolation][string interpolation] are a way of quickly and efficiently evaluating and formatting expressions and strings to a `str` type using the `f` (or `F`) prefix before the brackets (_like so `f'{object}'`_). - They can be used with all enclosing string types as: single quote `'`, double quote `"` and with multi-lines and escaping triple quotes `'''` or `"""`. - Any variables, expressions, or other types placed inside the `{}` are first evaluated, then converted to a `str`, then concatenated with any `str` outside the curly braces. +Introduced in [Python 3.6][pep-0498], [`f-strings`][f-string] (_short for "formatted-strings"_) or [literal string interpolation][string interpolation] are a way of quickly and efficiently evaluating and formatting expressions and strings to a `str` type using the `f` (or `F`) prefix before the brackets (_like so `f'{object}'`_). +They can be used with all enclosing string types as: single-line `'` or `"` and with multi-lines `'''` or `"""`. +Any variables, expressions, or other types placed inside the `{}` are first evaluated, then converted to a `str`, then concatenated with any `str` outside the curly braces. In this example, we insert two variable values in the sentence: one `str` and one `float`: @@ -23,8 +23,8 @@ In this example, we insert two variable values in the sentence: one `str` and on ... # The f-string, using the two values. # The .2f format code truncates so the value displays as 0.12. ->>> print(f'An {name} is approximately {value:.2f}.') -'An eighth is approximately 0.12.' +>>> f'An {name} is approximately {value:.2f}.' +'An eighth is approximately 0.12.' ``` The expressions evaluated can be almost anything. @@ -37,16 +37,16 @@ Some examples: >>> waves = {'water': 1, 'light': 3, 'sound': 5} # Using the name waves in an f-string. ->>> print(f'"A dict can be represented with f-string: {waves}."') +>>> f'"A dict can be represented with f-string: {waves}."' '"A dict can be represented with f-string: {\'water\': 1, \'light\': 3, \'sound\': 5}."' # Here, we pull a value from the dictionary by using the key ->>> print(f'Tenfold the value of "light" is {waves["light"]*10}.') +>>> f'Tenfold the value of "light" is {waves["light"] * 10}.' 'Tenfold the value of "light" is 30.' ``` Replacement fields (_the `{}` in the f-string_) support output control mechanisms such as width, alignment, precision. - This is the same [format specification mini-language][format-mini-language] that is used by the `str.format()` method. +This specification is started in the [format specification mini-language][format-mini-language]. A more complex example of an `f-string` that includes output control: @@ -61,24 +61,21 @@ A more complex example of an `f-string` that includes output control: # This example includes a function, str, a nested f-string, an arithmetic expression, # precision formatting, bracket escaping and object formatting. ->>> message = f'"Have a {"NICE".lower()} day, I will {verb} you after {f"{30e8*111_000:6.{precision}e}"} light-years."{{{the_end}}}' -... ->>> print(message) +>>> f'"Have a {"NICE".lower()} day, I will {verb} you after {f"{30e8 * 111_000:6.{precision}e}"} light-years."{{{the_end}}}' '"Have a nice day, I will meet you after 3.330e+14 light-years."{[\'end\', \'of\', \'transmission\']}' - ``` There are a few limitations to be aware of. -`f-string` expressions cannot be empty, they cannot contain comments, and for Python versions earlier than Python 3.7, they cannot contain `await` or `async for` clauses: +`f-string` expressions cannot be empty, they cannot contain comments. ```python ->>> print(f"An empty expression will error: {}") +>>> f"An empty expression will error: {}" SyntaxError: f-string: empty expression not allowed >>> word = 'word' ->>> print(f"""A comment in a triple quoted f-string will error: { +>>> f"""A comment in a triple quoted f-string will error: { word # I chose a nice variable -}""") +}""" SyntaxError: f-string expression part cannot include '#' ``` @@ -92,17 +89,16 @@ Also keep in mind that using expressions inside the `f-string` brackets `{}` is ## The `str.format()` Method The [`str.format()`][str-format] method replaces placeholders within the string with values fed as arguments to the function. - The placeholders are identified with named (`{price}`), numbered (`{0}` or indexed) or even empty (_positional_) placeholders `{}`. +The placeholders are identified with named (`{price}`), numbered (`{0}` or indexed) or even empty (_positional_) placeholders `{}`. For example: ```python # A named placeholder and a positional placeholder. ->>> print('My text: {placeholder_1} and {}.'.format(12, placeholder_1='named placeholder')) -... +>>> 'My text: {placeholder_1} and {}.'.format(12, placeholder_1='named placeholder') 'My text: named placeholder and 12.' ``` -As with `f-strings`, Pythons `str.format()` supports a whole range of [mini language format specifier][format-mini-language] that can be used to align text, convert, etc. +As with `f-strings`, Pythons `str.format()` supports a whole range of [mini language format specifier][format-mini-language] that can be used to align text, convert, etc. The complete formatting specifier pattern is `{[][!][:]}`: @@ -115,25 +111,24 @@ Example of conversions for a diacritical letter: ```python # Fills in the object at index zero, converted to a string. ->>> print('An e with an umlaut: {0!s}'.format('ë')) -An e with an umlaut: ë -... +>>> 'An e with an umlaut: {0!s}'.format('ë') +'An e with an umlaut: ë' + # Fills in the object at index zero, converted to a repr. ->>> print('An e with an umlaut object representation: {0!r}'.format('ë')) -An e with an umlaut object representation: 'ë' +>>> 'An e with an umlaut object representation: {0!r}'.format('ë') +"An e with an umlaut object representation: 'ë'" ... # Fills in the object at index zero, converted to ascii ->>> print('An e with an umlaut converted into ascii: {0!a}'.format('ë')) -An e with an umlaut converted into ascii: '\xeb' +>>> 'An e with an umlaut converted into ascii: {0!a}'.format('ë') +"An e with an umlaut converted into ascii: '\xeb'" -... # Fills in the object in the first position. # Then fills in the object in the second position formatted as a repr ->>> print('She said her name is not {} but {!r}.'.format('Chloe', 'Zoë')) +>>> 'She said her name is not {} but {!r}.'.format('Chloe', 'Zoë') "She said her name is not Chloe but 'Zoë'." ``` @@ -142,13 +137,12 @@ Example of using format specifiers: ```python # Formats the object at index 0 as a decimal with zero places, # then as a right-aligned binary number in an 8 character wide field. ->>> print("The number {0:d} has a representation in binary: '{0: >8b}'.".format(42)) -The number 42 has a representation in binary: ' 101010'. +>>> "The number {0:d} has a representation in binary: '{0: >8b}'.".format(42) +"The number 42 has a representation in binary: ' 101010'." ``` More examples are shown at the end of [this documentation][summary-string-format]. - ## `%` Formatting, or `printf()` Style Formatting Use of the `%` operator for formatting is the oldest method of string formatting in Python. @@ -157,16 +151,14 @@ It comes from the C language and allows the use of positional arguments to build This method has been superseded by both `f-strings` and `str.format()`, which is why the nickname for `%` formatting is _'Old Style'_. It can be still found in python 2 and/or legacy code. While using this method will work in Python 3.x, `%` formatting is usually avoided because it can be error-prone, is less efficient, has fewer options available, and any placeholder-argument mismatch can raise an exception. - Using the `%` operator is similar to [`printf()`][printf-style-docs], so it is also sometimes called _printf formatting_. - +Using the `%` operator is similar to [`printf()`][printf-style-docs], so it is also sometimes called _printf formatting_. ```python # Assigning a variable. ->> name = "Anna-conda" +>>> name = "Anna-conda" # Building a string using % ->> print("The snake's name is %s." % name) -... +>>> "The snake's name is %s." % name "The snake's name is Anna-conda." ``` @@ -179,33 +171,30 @@ If you want to add multiple variables to a string, you need to supply a [tuple][ >>> fruit = "grapes" # Building a string using % ->>> print("Surprisingly, %ss favorite snack was %s." %(name, fruit)) -Surprisingly, Billy the Kids favorite snack was grapes. +>>> "Surprisingly, %ss favorite snack was %s." %(name, fruit) +"Surprisingly, Billy the Kids favorite snack was grapes." ``` - ## Template Strings [`string.Template()`][string.Template()] is a class from the `string` module (_as opposed to the built-in `str` type_), which is part of the Python standard library, but has to be imported for use. Template strings support `$`-based substitution and are much simpler and less capable than the other options mentioned here, but can be very useful for when complicated internationalization is needed, or outside inputs need to be sanitized. - ```python ->> from string import Template +>>> from string import Template ->>> snake_name = "Anna-Conda" +>>> name = "Anna-Conda" # Creating a Template() with placeholder text ->> template_string = Template("The snake called `$snake_name` has escaped!") +>>> template_string = Template("The snake called `$snake_name` has escaped!") # Calling .substitute() to replace the placeholder with a value. ->> template_string.substitute(snake_name=name) +>>> template_string.substitute(snake_name=name) 'The snake called `Anna-Conda` has escaped!' ``` More information about `Template` string can be found in the Python [documentation][template-string]. - ## How Do You Choose which Formatting Method to Use? With all these options and mini-languages, how do you decide what to reach for when formatting Python strings? From 5bb59595d959f6c882a1cd5ea24fa5a615dbb844 Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 05:43:34 -0700 Subject: [PATCH 476/826] Removed footer macros, regen tests, all still pass (#3435) [no important files changed] --- exercises/practice/two-fer/.meta/template.j2 | 2 -- exercises/practice/two-fer/two_fer_test.py | 4 ---- exercises/practice/word-count/.meta/template.j2 | 3 --- exercises/practice/word-count/word_count_test.py | 4 ---- exercises/practice/word-search/.meta/template.j2 | 3 --- exercises/practice/word-search/word_search_test.py | 4 ---- exercises/practice/yacht/.meta/template.j2 | 2 -- exercises/practice/yacht/yacht_test.py | 4 ---- exercises/practice/zebra-puzzle/.meta/template.j2 | 2 -- exercises/practice/zebra-puzzle/zebra_puzzle_test.py | 4 ---- exercises/practice/zipper/.meta/template.j2 | 2 -- exercises/practice/zipper/zipper_test.py | 4 ---- 12 files changed, 38 deletions(-) diff --git a/exercises/practice/two-fer/.meta/template.j2 b/exercises/practice/two-fer/.meta/template.j2 index 51634eb105..5db1d53c7e 100644 --- a/exercises/practice/two-fer/.meta/template.j2 +++ b/exercises/practice/two-fer/.meta/template.j2 @@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] | to_snake }}("{{ case["input"]["name"] }}"), "{{ case["expected"] }}") {% endif %} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/two-fer/two_fer_test.py b/exercises/practice/two-fer/two_fer_test.py index 540930daf3..ea74f1165d 100644 --- a/exercises/practice/two-fer/two_fer_test.py +++ b/exercises/practice/two-fer/two_fer_test.py @@ -16,7 +16,3 @@ def test_a_name_given(self): def test_another_name_given(self): self.assertEqual(two_fer("Bob"), "One for Bob, one for me.") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/word-count/.meta/template.j2 b/exercises/practice/word-count/.meta/template.j2 index 2c91fdf963..7e41be6776 100644 --- a/exercises/practice/word-count/.meta/template.j2 +++ b/exercises/practice/word-count/.meta/template.j2 @@ -21,6 +21,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ test_case(case) }} {% endfor %} {%- endif %} - - -{{ macros.footer() }} diff --git a/exercises/practice/word-count/word_count_test.py b/exercises/practice/word-count/word_count_test.py index b63a9eb357..329301a38e 100644 --- a/exercises/practice/word-count/word_count_test.py +++ b/exercises/practice/word-count/word_count_test.py @@ -121,7 +121,3 @@ def test_non_alphanumeric(self): def test_multiple_apostrophes_ignored(self): self.assertEqual(count_words("''hey''"), {"hey": 1}) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/word-search/.meta/template.j2 b/exercises/practice/word-search/.meta/template.j2 index 309a2fc1a2..952da9e911 100644 --- a/exercises/practice/word-search/.meta/template.j2 +++ b/exercises/practice/word-search/.meta/template.j2 @@ -20,6 +20,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {%- endfor %} {% endfor %} - -{{ macros.footer() }} - diff --git a/exercises/practice/word-search/word_search_test.py b/exercises/practice/word-search/word_search_test.py index 6ee050418d..e5a32ceed7 100644 --- a/exercises/practice/word-search/word_search_test.py +++ b/exercises/practice/word-search/word_search_test.py @@ -310,7 +310,3 @@ def test_should_not_wrap_around_horizontally_to_find_a_word(self): def test_should_not_wrap_around_vertically_to_find_a_word(self): puzzle = WordSearch(["s", "u", "r", "a", "b", "c", "t"]) self.assertIsNone(puzzle.search("rust")) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/yacht/.meta/template.j2 b/exercises/practice/yacht/.meta/template.j2 index 4086f512fc..80eb31f7bc 100644 --- a/exercises/practice/yacht/.meta/template.j2 +++ b/exercises/practice/yacht/.meta/template.j2 @@ -16,5 +16,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/yacht/yacht_test.py b/exercises/practice/yacht/yacht_test.py index 5c262c8885..32b929942c 100644 --- a/exercises/practice/yacht/yacht_test.py +++ b/exercises/practice/yacht/yacht_test.py @@ -92,7 +92,3 @@ def test_choice(self): def test_yacht_as_choice(self): self.assertEqual(yacht.score([2, 2, 2, 2, 2], yacht.CHOICE), 10) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/zebra-puzzle/.meta/template.j2 b/exercises/practice/zebra-puzzle/.meta/template.j2 index 6b068c7e40..134ccdbf48 100644 --- a/exercises/practice/zebra-puzzle/.meta/template.j2 +++ b/exercises/practice/zebra-puzzle/.meta/template.j2 @@ -6,5 +6,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): def test_{{ case["description"] | to_snake }}(self): self.assertEqual({{ case["property"] | to_snake }}(), "{{ case["expected"] }}") {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py index 034d4cccf4..4b35afb7a7 100644 --- a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py +++ b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py @@ -14,7 +14,3 @@ def test_resident_who_drinks_water(self): def test_resident_who_owns_zebra(self): self.assertEqual(owns_zebra(), "Japanese") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/zipper/.meta/template.j2 b/exercises/practice/zipper/.meta/template.j2 index 9f239bc151..4e5de99523 100644 --- a/exercises/practice/zipper/.meta/template.j2 +++ b/exercises/practice/zipper/.meta/template.j2 @@ -52,5 +52,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {%- for case in cases %} {{ test_case(case) }} {%- endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/zipper/zipper_test.py b/exercises/practice/zipper/zipper_test.py index 5883e80df6..d770c92b3e 100644 --- a/exercises/practice/zipper/zipper_test.py +++ b/exercises/practice/zipper/zipper_test.py @@ -315,7 +315,3 @@ def test_different_paths_to_same_zipper(self): expected = Zipper.from_tree(final).right().to_tree() self.assertEqual(result, expected) - - -if __name__ == "__main__": - unittest.main() From 29a64a4889f94bafbd0062d7fc5052858523b25c Mon Sep 17 00:00:00 2001 From: Isaac Good Date: Fri, 14 Jul 2023 05:53:39 -0700 Subject: [PATCH 477/826] Remove Python 2 references from the track (#3437) * Remove Python 2 references from the track * Revert unintentional whitespace changes to test files [no important files changed] --- concepts/comparisons/about.md | 2 +- concepts/string-formatting/about.md | 2 +- docs/ABOUT.md | 2 +- docs/INSTALLATION.md | 4 ++-- exercises/concept/black-jack/.docs/introduction.md | 2 +- exercises/practice/circular-buffer/.meta/example.py | 2 +- exercises/practice/pythagorean-triplet/.meta/template.j2 | 3 --- .../practice/pythagorean-triplet/pythagorean_triplet_test.py | 4 ---- exercises/practice/sublist/sublist.py | 3 +-- pylintrc | 5 ----- reference/exercise-concepts/rna-transcription.md | 2 +- 11 files changed, 9 insertions(+), 22 deletions(-) diff --git a/concepts/comparisons/about.md b/concepts/comparisons/about.md index 568b6603c1..c2f5faaad9 100644 --- a/concepts/comparisons/about.md +++ b/concepts/comparisons/about.md @@ -82,7 +82,7 @@ False Strings (`str`) are compared [_lexicographically_][lexographic order], using their individual Unicode code points (_the result of passing each code point in the `str` to the built-in function [`ord()`][ord], which returns an `int`_). If all code points in both strings match and are _**in the same order**_, the two strings are considered equal. This comparison is done in a 'pair-wise' fashion - first-to-first, second-to-second, etc. -Unlike in Python 2.x, in Python 3.x, `str` and `bytes` cannot be directly coerced/compared. +In Python 3.x, `str` and `bytes` cannot be directly coerced/compared. ```python >>> 'Python' > 'Rust' diff --git a/concepts/string-formatting/about.md b/concepts/string-formatting/about.md index 46452b4163..f3b2756b76 100644 --- a/concepts/string-formatting/about.md +++ b/concepts/string-formatting/about.md @@ -149,7 +149,7 @@ Use of the `%` operator for formatting is the oldest method of string formatting It comes from the C language and allows the use of positional arguments to build a `str`. This method has been superseded by both `f-strings` and `str.format()`, which is why the nickname for `%` formatting is _'Old Style'_. -It can be still found in python 2 and/or legacy code. +It can be still found in Python 2 and/or legacy code. While using this method will work in Python 3.x, `%` formatting is usually avoided because it can be error-prone, is less efficient, has fewer options available, and any placeholder-argument mismatch can raise an exception. Using the `%` operator is similar to [`printf()`][printf-style-docs], so it is also sometimes called _printf formatting_. diff --git a/docs/ABOUT.md b/docs/ABOUT.md index 05c56799ac..f0d52389bf 100644 --- a/docs/ABOUT.md +++ b/docs/ABOUT.md @@ -22,7 +22,7 @@ The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is Tests and tooling for this track currently support `3.7` - `3.10.6` (_tests_) and [`Python 3.11`][311-new-features] (_tooling_). It is highly recommended that students upgrade to at least `Python 3.8`, as some features used by this track may not be supported in earlier versions. -That being said, most of the exercises will work with `Python 3.6+`, and many are compatible with `Python 2.7+`. +That being said, most of the exercises will work with `Python 3.6+`. But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases]. We will try to note when a feature is only available in a certain version. diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 8bf42a0452..c8eb68ca46 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -6,7 +6,7 @@ Real Python also offers a [nice guide][helpful guide] to installation on various Finally, these posts by Brett Cannon [A quick-and-dirty guide][quick-and-dirty] and [Why you should use `python -m pip`][python-m-pip], give very helpful advice on how to manage Python installations and packages. **Note for MacOS users:** prior to MacOS Monterey (12.3), `Python 2.7` came pre-installed with the operating system. -Using `Python 2.7` with Exercism or most other programs is not recommended. +Using `Python 2.7` with Exercism or most other programs is not supported. You should instead install [Python 3][Python-three downloads] via one of the methods detailed below. As of MacOS Monterey (12.3), no version of Python will be pre-installed via MacOS. @@ -20,7 +20,7 @@ Some quick links into the documentation by operating system: Exercism tests and tooling currently support `3.7` - `3.10.6` (_tests_) and [`Python 3.11`][311-new-features] (_tooling_). Exceptions to this support are noted where they occur. -Most of the exercises will work with `Python 3.6+`, and many are compatible with `Python 2.7+`. +Most of the exercises will work with `Python 3.6+`. But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases]. diff --git a/exercises/concept/black-jack/.docs/introduction.md b/exercises/concept/black-jack/.docs/introduction.md index b79091f4ff..207229359d 100644 --- a/exercises/concept/black-jack/.docs/introduction.md +++ b/exercises/concept/black-jack/.docs/introduction.md @@ -80,7 +80,7 @@ False Unlike numbers, strings (`str`) are compared [_lexicographically_][lexographic order], using their individual Unicode code points (_the result of passing each code point in the `str` to the built-in function [`ord()`][ord], which returns an `int`_). If all code points in both strings match and are _**in the same order**_, the two strings are considered equal. This comparison is done in a 'pair-wise' fashion - first-to-first, second-to-second, etc. -Unlike in Python 2.x, in Python 3.x, `str` and `bytes` cannot be directly coerced/compared. +In Python 3.x, `str` and `bytes` cannot be directly coerced/compared. ```python >>> 'Python' > 'Rust' diff --git a/exercises/practice/circular-buffer/.meta/example.py b/exercises/practice/circular-buffer/.meta/example.py index 8438b9839f..538cc7bc5e 100644 --- a/exercises/practice/circular-buffer/.meta/example.py +++ b/exercises/practice/circular-buffer/.meta/example.py @@ -25,7 +25,7 @@ def __init__(self, capacity): self.read_point = 0 self.write_point = 0 - # (protected) helper method to support python 2/3 + # (protected) helper method def _update_buffer(self, data): try: self.buffer[self.write_point] = data diff --git a/exercises/practice/pythagorean-triplet/.meta/template.j2 b/exercises/practice/pythagorean-triplet/.meta/template.j2 index b3836a5442..e52cda4e23 100644 --- a/exercises/practice/pythagorean-triplet/.meta/template.j2 +++ b/exercises/practice/pythagorean-triplet/.meta/template.j2 @@ -2,9 +2,6 @@ {{ macros.header() }} -# Python 2/3 compatibility -if not hasattr(unittest.TestCase, 'assertCountEqual'): - unittest.TestCase.assertCountEqual = unittest.TestCase.assertItemsEqual class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py index 70d501c4fb..bb094c909d 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py +++ b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py @@ -6,10 +6,6 @@ # Tests adapted from `problem-specifications//canonical-data.json` -# Python 2/3 compatibility -if not hasattr(unittest.TestCase, "assertCountEqual"): - unittest.TestCase.assertCountEqual = unittest.TestCase.assertItemsEqual - class PythagoreanTripletTest(unittest.TestCase): def test_triplets_whose_sum_is_12(self): diff --git a/exercises/practice/sublist/sublist.py b/exercises/practice/sublist/sublist.py index 428d3aa656..35410301d5 100644 --- a/exercises/practice/sublist/sublist.py +++ b/exercises/practice/sublist/sublist.py @@ -1,8 +1,7 @@ """ This exercise stub and the test suite contain several enumerated constants. -Since Python 2 does not have the enum module, the idiomatic way to write -enumerated constants has traditionally been a NAME assigned to an arbitrary, +Enumerated constants can be done with a NAME assigned to an arbitrary, but unique value. An integer is traditionally used because it’s memory efficient. It is a common practice to export both constants and functions that work with diff --git a/pylintrc b/pylintrc index 745e6f039a..09795978bc 100644 --- a/pylintrc +++ b/pylintrc @@ -341,11 +341,6 @@ known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant, absl -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - [CLASSES] diff --git a/reference/exercise-concepts/rna-transcription.md b/reference/exercise-concepts/rna-transcription.md index b2b1294681..004ded9511 100644 --- a/reference/exercise-concepts/rna-transcription.md +++ b/reference/exercise-concepts/rna-transcription.md @@ -2,7 +2,7 @@ ## Example implementation -Modified from the existing [example.py](https://github.com/exercism/python/blob/master/exercises/rna-transcription/example.py) to remove Python 2 compatiblity noise: +Taken from the existing [example.py](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py): ```python DNA_TO_RNA = str.maketrans("AGCT", "UCGA") From 21658267b4f3b626a1a6f636ecdbed3f5eb79a99 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Fri, 14 Jul 2023 18:28:10 +0530 Subject: [PATCH 478/826] Approaches for Robot Name (#3451) * add intro and config * add content * compare approaches --- .../robot-name/.approaches/config.json | 21 +++++++ .../robot-name/.approaches/introduction.md | 59 +++++++++++++++++++ .../mass-name-generation/content.md | 49 +++++++++++++++ .../mass-name-generation/snippet.txt | 8 +++ .../.approaches/name-on-the-fly/content.md | 52 ++++++++++++++++ .../.approaches/name-on-the-fly/snippet.txt | 8 +++ 6 files changed, 197 insertions(+) create mode 100644 exercises/practice/robot-name/.approaches/config.json create mode 100644 exercises/practice/robot-name/.approaches/introduction.md create mode 100644 exercises/practice/robot-name/.approaches/mass-name-generation/content.md create mode 100644 exercises/practice/robot-name/.approaches/mass-name-generation/snippet.txt create mode 100644 exercises/practice/robot-name/.approaches/name-on-the-fly/content.md create mode 100644 exercises/practice/robot-name/.approaches/name-on-the-fly/snippet.txt diff --git a/exercises/practice/robot-name/.approaches/config.json b/exercises/practice/robot-name/.approaches/config.json new file mode 100644 index 0000000000..c71235ec7e --- /dev/null +++ b/exercises/practice/robot-name/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": ["safwansamsudeen"] + }, + "approaches": [ + { + "uuid": "94d82d63-0a20-44df-ad9b-14aba7c076ae", + "slug": "mass-name-generation", + "title": "Mass Name Generation", + "blurb": "Select from all possible names", + "authors": ["safwansamsudeen"] + }, + { + "uuid": "20ca6d56-ab48-46be-a04e-98736109be0a", + "slug": "name-on-the-fly", + "title": "Find name on the fly", + "blurb": "Generate name and check that it hasn't been used", + "authors": ["safwansamsudeen"] + } + ] +} diff --git a/exercises/practice/robot-name/.approaches/introduction.md b/exercises/practice/robot-name/.approaches/introduction.md new file mode 100644 index 0000000000..9dc810ed5e --- /dev/null +++ b/exercises/practice/robot-name/.approaches/introduction.md @@ -0,0 +1,59 @@ +# Introduction +Robot Name in Python is an interesting exercise for practising randomness. + +## General Guidance +Two ways immedietely come to mind: generate all the possible names and then return them sequentially, or generate a random name and ensure that it's not been previously used. + +Randomness can be a little, well, random, so **it's very easy to have an incorrect solution and still pass the tests**. It's strongly recommended to submit your solution for Code Review. + +## Approach: mass name generation +We'd first have to generate all the possible names, shuffle them, and then use `next` (the simplest way) or maintain a `current_index` and get the name. +Here's a possible way to do it: + +```python +from itertools import product +from random import shuffle +from string import ascii_uppercase as letters + +letter_pairs = (''.join(p) for p in product(letters, letters)) +numbers = (str(i).zfill(3) for i in range(1000)) +names = [l + n for l, n in product(letter_pairs, numbers)] +shuffle(names) +NAMES = iter(names) +class Robot(object): + def __init__(self): + self.reset() + def reset(self): + self.name = next(NAMES) +``` +Note that selecting randomly from the list of all names would be incorrect, as there's a possibility of the name being repeated. +For more detail and explanation of the code, [read here][approach-mass-name-generation]. + +## Approach: name on the fly +Another approach is to generate the name on the fly and add it to a cache or a store, and checking if the generated name hasn't been used previously. + + +A possible way to implement this: +```python +from string import ascii_uppercase, digits +from random import choices + + +cache = set() + + +class Robot: + def __get_name(self): + return ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3)) + + def reset(self): + while (name := self.__get_name()) in cache: + pass + cache.add(name) + self.name = name + + def __init__(self): + self.reset() +``` + +For more detail and different ways to implement this, [read here][approach-name-on-the-fly]. \ No newline at end of file diff --git a/exercises/practice/robot-name/.approaches/mass-name-generation/content.md b/exercises/practice/robot-name/.approaches/mass-name-generation/content.md new file mode 100644 index 0000000000..6e5fad83e9 --- /dev/null +++ b/exercises/practice/robot-name/.approaches/mass-name-generation/content.md @@ -0,0 +1,49 @@ +# Mass Name Generation +We'd first have to generate all the possible names, shuffle them, and then use `next` (the simplest way) or maintain a `current_index` and get the name. +Note that selecting randomly from the list of all names would be incorrect, as there's a possibility of the name being repeated. + +Here's a possible way to do it: + +```python +from itertools import product +from random import shuffle +from string import ascii_uppercase + +letter_pairs = (''.join(p) for p in product(ascii_uppercase, ascii_uppercase)) +numbers = (str(i).zfill(3) for i in range(1000)) +names = [l + n for l, n in product(letter_pairs, numbers)] + +shuffle(names) +NAMES = iter(names) + +class Robot(object): + def __init__(self): + self.reset() + def reset(self): + self.name = next(NAMES) +``` + +The first few lines of the mass name generation uses [`itertools.product`][itertools-product]. +The resultant code is a simplification of: +```python +letter_pairs = (''.join((l1, l2)) for l1 in ascii_uppercase for l2 in ascii_uppercase) +numbers = (str(i).zfill(3) for i in range(1000)) +names = [l + n for l in letter_pairs for n in numbers] +``` + +After the name generation, the names are shuffled - using the [default `seed`][random-seed] in the `random` module (the current timestamp). +When the tests reseed `random`, this has no effect as the names were shuffled before that. + +We then set `NAMES` to the iterable of names, and in `reset`, set the robot's name to the `next(name)`. +If you'd like, read more on [`iter` and `next`][iter-and-next]. + +Unlike the on the fly approach, this has a relatively short "generation" time, because we're merely giving the `next` name instead of generating it. +However, this has a huge startup memory and time cost, as 676,000 strings have to be calculated and stored. +For an approximate calculation, 676,000 strings * 5 characters / string * 1 byte / character gives 3380000 bytes or 3.38 MB of RAM - and that's just the memory aspect of it. +Sounds small, but it's relatively very expensive at the beginning. + +Thus, this approach is inefficient in cases where only a small amount of names are needed _and_ the time to set/reset the robot isn't crucial. + +[random-seed]: https://docs.python.org/3/library/random.html#random.seed +[iter-and-next]: https://www.programiz.com/python-programming/methods/built-in/iter +[itertools-product]: https://www.hackerrank.com/challenges/itertools-product/problem \ No newline at end of file diff --git a/exercises/practice/robot-name/.approaches/mass-name-generation/snippet.txt b/exercises/practice/robot-name/.approaches/mass-name-generation/snippet.txt new file mode 100644 index 0000000000..341230946b --- /dev/null +++ b/exercises/practice/robot-name/.approaches/mass-name-generation/snippet.txt @@ -0,0 +1,8 @@ +... +names = [l + n for l, n in product(letter_pairs, numbers)] +shuffle(names) +NAMES = iter(names) + +class Robot(object): + def reset(self): + self.name = next(NAMES) \ No newline at end of file diff --git a/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md b/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md new file mode 100644 index 0000000000..0aa9f9a3fa --- /dev/null +++ b/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md @@ -0,0 +1,52 @@ +# Find name on the fly +We generate the name on the fly and add it to a cache or a store, and checking if the generated name hasn't been used previously. + +A possible way to implement this: +```python +from string import ascii_uppercase, digits +from random import choices + +cache = set() + + +class Robot: + def __get_name(self): + return ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3)) + + def reset(self): + while (name := self.__get_name()) in cache: + pass + cache.add(name) + self.name = name + + def __init__(self): + self.reset() +``` +We use a `set` for the cache as it has a low access time, and we don't need the preservation of order or the ability to be indexed. + +This way is merely one of the many to generate the name. +Another way might be to use `randrange` along with `zfill` for the number part, and a double `random.choice` / `random.choice` on `itertools.product` to generate the letter part. +This is the shortest way, and best utilizes the Python standard library. + +As we're using a `while` loop to check for the name generation, it's convenient to store the local `name` using the [walrus operator][walrus-operator]. +It's also possible to find the name before the loop and find it again inside the loop, but that would unnecessary repetition. +A helper method ([private][private-helper-methods] in this case) makes your code cleaner, but it's equally valid to have the code in the loop itself: +```python +def reset(self): + while (name := ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3))) in cache: + pass + cache.add(name) + self.name = name +``` + +We call `reset` from `__init__` - it's syntactically valid to do it the other way round, but it's not considered good practice to call [dunder methods][dunder-methods] directly. + +This has almost no startup time and memory, apart from declaring an empty `set`. +Note that the _generation_ time is the same as the mass generation approach, as a similar method is used. +However, as the name is generated at the time of setting/resetting, the method time itself is higher. + +In the long run, if many names are generated, this is inefficient, since collisions will start being generated more often than unique names. + +[walrus-operator]: https://realpython.com/python-walrus-operator/ +[private-helper-methods]: https://www.geeksforgeeks.org/private-methods-in-python/ +[dunder-methods]: https://dbader.org/blog/python-dunder-methods \ No newline at end of file diff --git a/exercises/practice/robot-name/.approaches/name-on-the-fly/snippet.txt b/exercises/practice/robot-name/.approaches/name-on-the-fly/snippet.txt new file mode 100644 index 0000000000..e6ab23e7be --- /dev/null +++ b/exercises/practice/robot-name/.approaches/name-on-the-fly/snippet.txt @@ -0,0 +1,8 @@ +cache = set() +class Robot: + def reset(self): + while (name := ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3))) in cache: + pass + cache.add(name) + self.name = name + def __init__(self): self.reset() \ No newline at end of file From 3162bd4fe3e93efb07dcc04a23f57d7067078da1 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Jul 2023 06:50:28 -0700 Subject: [PATCH 479/826] Synced instructions and introduction files from problem specs. (#3423) --- .../practice/accumulate/.docs/instructions.md | 8 ++---- .../binary-search/.docs/instructions.md | 2 +- .../practice/binary/.docs/instructions.md | 9 +++--- .../practice/book-store/.docs/instructions.md | 28 +++++++++---------- .../practice/bowling/.docs/instructions.md | 2 +- .../practice/darts/.docs/instructions.md | 2 +- .../error-handling/.docs/instructions.md | 8 ++---- .../practice/gigasecond/.docs/introduction.md | 4 +-- .../hexadecimal/.docs/instructions.md | 3 +- .../.docs/instructions.md | 6 ++-- .../linked-list/.docs/instructions.md | 4 +-- .../nucleotide-count/.docs/instructions.md | 6 ++-- .../practice/octal/.docs/instructions.md | 8 ++---- .../practice/pangram/.docs/instructions.md | 2 +- .../practice/pangram/.docs/introduction.md | 4 +-- .../.docs/instructions.md | 7 ++--- .../queen-attack/.docs/instructions.md | 2 +- .../roman-numerals/.docs/instructions.md | 2 +- .../saddle-points/.docs/instructions.md | 6 ++-- .../saddle-points/.docs/introduction.md | 12 ++++---- .../secret-handshake/.docs/instructions.md | 1 + .../practice/sieve/.docs/instructions.md | 4 +-- .../practice/strain/.docs/instructions.md | 15 ++++------ .../sum-of-multiples/.docs/instructions.md | 6 ++-- .../practice/trinary/.docs/instructions.md | 13 ++++----- .../practice/two-fer/.docs/instructions.md | 12 ++++---- 26 files changed, 82 insertions(+), 94 deletions(-) diff --git a/exercises/practice/accumulate/.docs/instructions.md b/exercises/practice/accumulate/.docs/instructions.md index 435e0b3246..c25a03fab1 100644 --- a/exercises/practice/accumulate/.docs/instructions.md +++ b/exercises/practice/accumulate/.docs/instructions.md @@ -1,9 +1,6 @@ # Instructions -Implement the `accumulate` operation, which, given a collection and an -operation to perform on each element of the collection, returns a new -collection containing the result of applying that operation to each element of -the input collection. +Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection. Given the collection of numbers: @@ -21,6 +18,5 @@ Check out the test suite to see the expected function signature. ## Restrictions -Keep your hands off that collect/map/fmap/whatchamacallit functionality -provided by your standard library! +Keep your hands off that collect/map/fmap/whatchamacallit functionality provided by your standard library! Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index d7f1c89922..aa1946cfb0 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -11,7 +11,7 @@ Binary search only works when a list has been sorted. The algorithm looks like this: -- Find the middle element of a sorted list and compare it with the item we're looking for. +- Find the middle element of a *sorted* list and compare it with the item we're looking for. - If the middle element is our item, then we're done! - If the middle element is greater than our item, we can eliminate that element and all the elements **after** it. - If the middle element is less than our item, we can eliminate that element and all the elements **before** it. diff --git a/exercises/practice/binary/.docs/instructions.md b/exercises/practice/binary/.docs/instructions.md index 3cfde215fc..046fd3e099 100644 --- a/exercises/practice/binary/.docs/instructions.md +++ b/exercises/practice/binary/.docs/instructions.md @@ -2,9 +2,9 @@ Convert a binary number, represented as a string (e.g. '101010'), to its decimal equivalent using first principles. -Implement binary to decimal conversion. Given a binary input -string, your program should produce a decimal output. The -program should handle invalid inputs. +Implement binary to decimal conversion. +Given a binary input string, your program should produce a decimal output. +The program should handle invalid inputs. ## Note @@ -15,8 +15,7 @@ program should handle invalid inputs. Decimal is a base-10 system. -A number 23 in base 10 notation can be understood -as a linear combination of powers of 10: +A number 23 in base 10 notation can be understood as a linear combination of powers of 10: - The rightmost digit gets multiplied by 10^0 = 1 - The next number gets multiplied by 10^1 = 10 diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md index 341ad01fbd..906eb58761 100644 --- a/exercises/practice/book-store/.docs/instructions.md +++ b/exercises/practice/book-store/.docs/instructions.md @@ -12,9 +12,9 @@ If you buy 4 different books, you get a 20% discount. If you buy all 5, you get a 25% discount. -Note: that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8. +Note that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8. -Your mission is to write a piece of code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible. +Your mission is to write code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible. For example, how much does this basket of books cost? @@ -26,36 +26,36 @@ For example, how much does this basket of books cost? One way of grouping these 8 books is: -- 1 group of 5 --> 25% discount (1st,2nd,3rd,4th,5th) -- +1 group of 3 --> 10% discount (1st,2nd,3rd) +- 1 group of 5 (1st, 2nd,3rd, 4th, 5th) +- 1 group of 3 (1st, 2nd, 3rd) This would give a total of: - 5 books at a 25% discount -- +3 books at a 10% discount +- 3 books at a 10% discount Resulting in: -- 5 × (8 - 2.00) = 5 × 6.00 = $30.00 -- +3 × (8 - 0.80) = 3 × 7.20 = $21.60 +- 5 × (100% - 25%) * $8 = 5 × $6.00 = $30.00, plus +- 3 × (100% - 10%) * $8 = 3 × $7.20 = $21.60 -For a total of $51.60 +Which equals $51.60. However, a different way to group these 8 books is: -- 1 group of 4 books --> 20% discount (1st,2nd,3rd,4th) -- +1 group of 4 books --> 20% discount (1st,2nd,3rd,5th) +- 1 group of 4 books (1st, 2nd, 3rd, 4th) +- 1 group of 4 books (1st, 2nd, 3rd, 5th) This would give a total of: - 4 books at a 20% discount -- +4 books at a 20% discount +- 4 books at a 20% discount Resulting in: -- 4 × (8 - 1.60) = 4 × 6.40 = $25.60 -- +4 × (8 - 1.60) = 4 × 6.40 = $25.60 +- 4 × (100% - 20%) * $8 = 4 × $6.40 = $25.60, plus +- 4 × (100% - 20%) * $8 = 4 × $6.40 = $25.60 -For a total of $51.20 +Which equals $51.20. And $51.20 is the price with the biggest discount. diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md index 48a2fedcf4..ddce7ee489 100644 --- a/exercises/practice/bowling/.docs/instructions.md +++ b/exercises/practice/bowling/.docs/instructions.md @@ -36,7 +36,7 @@ Frame 3 is (9 + 0) = 9 This means the current running total is 48. The tenth frame in the game is a special case. -If someone throws a strike or a spare then they get a fill ball. +If someone throws a spare or a strike then they get one or two fill balls respectively. Fill balls exist to calculate the total of the 10th frame. Scoring a strike or spare on the fill ball does not give the player more fill balls. The total value of the 10th frame is the total number of pins knocked down. diff --git a/exercises/practice/darts/.docs/instructions.md b/exercises/practice/darts/.docs/instructions.md index 7af7428a06..70f0e53da7 100644 --- a/exercises/practice/darts/.docs/instructions.md +++ b/exercises/practice/darts/.docs/instructions.md @@ -12,7 +12,7 @@ In our particular instance of the game, the target rewards 4 different amounts o - If the dart lands in the inner circle of the target, player earns 10 points. The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. -Of course, they are all centered at the same point (that is, the circles are [concentric][] defined by the coordinates (0, 0). +Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0). Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point. diff --git a/exercises/practice/error-handling/.docs/instructions.md b/exercises/practice/error-handling/.docs/instructions.md index 7bc1a0856b..25dd4d2928 100644 --- a/exercises/practice/error-handling/.docs/instructions.md +++ b/exercises/practice/error-handling/.docs/instructions.md @@ -2,9 +2,7 @@ Implement various kinds of error handling and resource management. -An important point of programming is how to handle errors and close -resources even if errors occur. +An important point of programming is how to handle errors and close resources even if errors occur. -This exercise requires you to handle various errors. Because error handling -is rather programming language specific you'll have to refer to the tests -for your track to see what's exactly required. +This exercise requires you to handle various errors. +Because error handling is rather programming language specific you'll have to refer to the tests for your track to see what's exactly required. diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md index 74afaa994f..18a3dc2005 100644 --- a/exercises/practice/gigasecond/.docs/introduction.md +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -13,7 +13,7 @@ Then we can use metric system prefixes for writing large numbers of seconds in m - Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). - And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. -```exercism/note +~~~~exercism/note If we ever colonize Mars or some other planet, measuring time is going to get even messier. If someone says "year" do they mean a year on Earth or a year on Mars? @@ -21,4 +21,4 @@ The idea for this exercise came from the science fiction novel ["A Deepness in t In it the author uses the metric system as the basis for time measurements. [vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ -``` +~~~~ diff --git a/exercises/practice/hexadecimal/.docs/instructions.md b/exercises/practice/hexadecimal/.docs/instructions.md index 3e5afa77c6..a3c648e32f 100644 --- a/exercises/practice/hexadecimal/.docs/instructions.md +++ b/exercises/practice/hexadecimal/.docs/instructions.md @@ -2,7 +2,6 @@ Convert a hexadecimal number, represented as a string (e.g. "10af8c"), to its decimal equivalent using first principles (i.e. no, you may not use built-in or external libraries to accomplish the conversion). -On the web we use hexadecimal to represent colors, e.g. green: 008000, -teal: 008080, navy: 000080). +On the web we use hexadecimal to represent colors, e.g. green: 008000, teal: 008080, navy: 000080). The program should handle invalid hexadecimal strings. diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md index 93469a09cc..2347ab262c 100644 --- a/exercises/practice/killer-sudoku-helper/.docs/instructions.md +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -56,8 +56,8 @@ The screenshots above have been generated using [F-Puzzles.com](https://www.f-pu [sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/ [killer-guide]: https://masteringsudoku.com/killer-sudoku/ -[one-solution-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example1.png -[four-solutions-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example2.png -[not-possible-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example3.png +[one-solution-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example1.png +[four-solutions-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example2.png +[not-possible-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example3.png [clover-puzzle]: https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R [goodliffe-video]: https://youtu.be/c_NjEbFEeW0?t=1180 diff --git a/exercises/practice/linked-list/.docs/instructions.md b/exercises/practice/linked-list/.docs/instructions.md index a47942d73d..edf4055b38 100644 --- a/exercises/practice/linked-list/.docs/instructions.md +++ b/exercises/practice/linked-list/.docs/instructions.md @@ -13,7 +13,7 @@ Sometimes a station gets closed down, and in that case the station needs to be r The size of a route is measured not by how far the train travels, but by how many stations it stops at. -```exercism/note +~~~~exercism/note The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. As the name suggests, it is a list of nodes that are linked together. It is a list of "nodes", where each node links to its neighbor or neighbors. @@ -23,4 +23,4 @@ In a **doubly linked list** each node links to both the node that comes before, If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. [intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d -``` +~~~~ diff --git a/exercises/practice/nucleotide-count/.docs/instructions.md b/exercises/practice/nucleotide-count/.docs/instructions.md index 57667134b7..548d9ba5a5 100644 --- a/exercises/practice/nucleotide-count/.docs/instructions.md +++ b/exercises/practice/nucleotide-count/.docs/instructions.md @@ -1,10 +1,12 @@ # Instructions -Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. All known life depends on DNA! +Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. +All known life depends on DNA! > Note: You do not need to understand anything about nucleotides or DNA to complete this exercise. -DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! +DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. +A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! We call the order of these nucleotides in a bit of DNA a "DNA sequence". We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides. diff --git a/exercises/practice/octal/.docs/instructions.md b/exercises/practice/octal/.docs/instructions.md index 81f108384b..65ce135c6f 100644 --- a/exercises/practice/octal/.docs/instructions.md +++ b/exercises/practice/octal/.docs/instructions.md @@ -1,11 +1,9 @@ # Instructions -Convert an octal number, represented as a string (e.g. '1735263'), to its -decimal equivalent using first principles (i.e. no, you may not use built-in or -external libraries to accomplish the conversion). +Convert an octal number, represented as a string (e.g. '1735263'), to its decimal equivalent using first principles (i.e. no, you may not use built-in or external libraries to accomplish the conversion). -Implement octal to decimal conversion. Given an octal input -string, your program should produce a decimal output. +Implement octal to decimal conversion. +Given an octal input string, your program should produce a decimal output. ## Note diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index d5698bc2a2..817c872d90 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -5,4 +5,4 @@ Your task is to figure out if a sentence is a pangram. A pangram is a sentence using every letter of the alphabet at least once. It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). -For this exercise we only use the basic letters used in the English alphabet: `a` to `z`. +For this exercise, a sentence is a pangram if it contains each of the 26 letters in the English alphabet. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md index d38fa341df..32b6f1fc31 100644 --- a/exercises/practice/pangram/.docs/introduction.md +++ b/exercises/practice/pangram/.docs/introduction.md @@ -7,10 +7,10 @@ To give a comprehensive sense of the font, the random sentences should use **all They're running a competition to get suggestions for sentences that they can use. You're in charge of checking the submissions to see if they are valid. -```exercism/note +~~~~exercism/note Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". The best known English pangram is: > The quick brown fox jumps over the lazy dog. -``` +~~~~ diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md index a5b936c5e2..85abcf86a4 100644 --- a/exercises/practice/parallel-letter-frequency/.docs/instructions.md +++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.md @@ -2,7 +2,6 @@ Count the frequency of letters in texts using parallel computation. -Parallelism is about doing things in parallel that can also be done -sequentially. A common example is counting the frequency of letters. -Create a function that returns the total frequency of each letter in a -list of texts and that employs parallelism. +Parallelism is about doing things in parallel that can also be done sequentially. +A common example is counting the frequency of letters. +Create a function that returns the total frequency of each letter in a list of texts and that employs parallelism. diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md index dce0fc2985..ad7ea95479 100644 --- a/exercises/practice/queen-attack/.docs/instructions.md +++ b/exercises/practice/queen-attack/.docs/instructions.md @@ -21,5 +21,5 @@ So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) a b c d e f g h ``` -You are also be able to answer whether the queens can attack each other. +You are also able to answer whether the queens can attack each other. In this case, that answer would be yes, they can, because both pieces share a diagonal. diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md index bb7e909dbf..247ea0892e 100644 --- a/exercises/practice/roman-numerals/.docs/instructions.md +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -36,6 +36,6 @@ In Roman numerals 1990 is MCMXC: 2000=MM 8=VIII -Learn more about [Roman numberals on Wikipedia][roman-numerals]. +Learn more about [Roman numerals on Wikipedia][roman-numerals]. [roman-numerals]: https://wiki.imperivm-romanvm.com/wiki/Roman_Numerals diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index d861388e43..c585568b46 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -5,20 +5,22 @@ Your task is to find the potential trees where you could build your tree house. The data company provides the data as grids that show the heights of the trees. The rows of the grid represent the east-west direction, and the columns represent the north-south direction. -An acceptable tree will be the the largest in its row, while being the smallest in its column. +An acceptable tree will be the largest in its row, while being the smallest in its column. A grid might not have any good trees at all. Or it might have one, or even several. Here is a grid that has exactly one candidate tree. +```text 1 2 3 4 |----------- 1 | 9 8 7 8 2 | 5 3 2 4 <--- potential tree house at row 2, column 1, for tree with height 5 3 | 6 6 7 1 +``` -- Row 2 has values 5, 3, and 1. The largest value is 5. +- Row 2 has values 5, 3, 2, and 4. The largest value is 5. - Column 1 has values 9, 5, and 6. The smallest value is 5. So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md index b582efbd21..34b2c77e0c 100644 --- a/exercises/practice/saddle-points/.docs/introduction.md +++ b/exercises/practice/saddle-points/.docs/introduction.md @@ -1,9 +1,11 @@ # Introduction -You are planning on building a tree house in the woods near your house so that you can watch the sun rise and set. +You plan to build a tree house in the woods near your house so that you can watch the sun rise and set. -You've obtained data from a local survey company that shows the heights of all the trees in each rectangular section of the map. -You need to analyze each grid on the map to find the perfect tree for your tree house. +You've obtained data from a local survey company that show the height of every tree in each rectangular section of the map. +You need to analyze each grid on the map to find good trees for your tree house. -The best tree will be the tallest tree compared to all the other trees to the east and west, so that you have the best possible view of the sunrises and sunsets. -You don't like climbing too much, so the perfect tree will also be the shortest among all the trees to the north and to the south. +A good tree is both: + +- taller than every tree to the east and west, so that you have the best possible view of the sunrises and sunsets. +- shorter than every tree to the north and south, to minimize the amount of tree climbing. diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index 77136cf0f7..d2120b9bf2 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -43,5 +43,6 @@ jump, double blink ~~~~exercism/note If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. + [intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa ~~~~ diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index ec14620ce4..3adf1d551b 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -18,11 +18,11 @@ Then you repeat the following steps: You keep repeating these steps until you've gone through every number in your list. At the end, all the unmarked numbers are prime. -```exercism/note +~~~~exercism/note [Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. A good first test is to check that you do not use division or remainder operations. [eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes -``` +~~~~ diff --git a/exercises/practice/strain/.docs/instructions.md b/exercises/practice/strain/.docs/instructions.md index 370eb2216f..3469ae6579 100644 --- a/exercises/practice/strain/.docs/instructions.md +++ b/exercises/practice/strain/.docs/instructions.md @@ -1,9 +1,7 @@ # Instructions -Implement the `keep` and `discard` operation on collections. Given a collection -and a predicate on the collection's elements, `keep` returns a new collection -containing those elements where the predicate is true, while `discard` returns -a new collection containing those elements where the predicate is false. +Implement the `keep` and `discard` operation on collections. +Given a collection and a predicate on the collection's elements, `keep` returns a new collection containing those elements where the predicate is true, while `discard` returns a new collection containing those elements where the predicate is false. For example, given the collection of numbers: @@ -23,12 +21,9 @@ While your discard operation should produce: Note that the union of keep and discard is all the elements. -The functions may be called `keep` and `discard`, or they may need different -names in order to not clash with existing functions or concepts in your -language. +The functions may be called `keep` and `discard`, or they may need different names in order to not clash with existing functions or concepts in your language. ## Restrictions -Keep your hands off that filter/reject/whatchamacallit functionality -provided by your standard library! Solve this one yourself using other -basic tools instead. +Keep your hands off that filter/reject/whatchamacallit functionality provided by your standard library! +Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index e9a25636e5..d69f890e9d 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -7,12 +7,12 @@ The points awarded depend on two things: - The level (a number) that the player completed. - The base value of each magical item collected by the player during that level. -The energy points are awarded according to the following rules: +The energy points are awarded according to the following rules: 1. For each magical item, take the base value and find all the multiples of that value that are less than the level number. 2. Combine the sets of numbers. 3. Remove any duplicates. -4. Calculate the sum of all the numbers that are left. +4. Calculate the sum of all the numbers that are left. Let's look at an example: @@ -24,4 +24,4 @@ To calculate the energy points earned by the player, we need to find all the uni - Multiples of 5 less than 20: `{5, 10, 15}` - Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18}` - Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78` -- Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5 +- Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5. diff --git a/exercises/practice/trinary/.docs/instructions.md b/exercises/practice/trinary/.docs/instructions.md index 3638ddb74b..d38e3b5bbf 100644 --- a/exercises/practice/trinary/.docs/instructions.md +++ b/exercises/practice/trinary/.docs/instructions.md @@ -1,15 +1,13 @@ # Instructions -Convert a trinary number, represented as a string (e.g. '102012'), to its -decimal equivalent using first principles. +Convert a trinary number, represented as a string (e.g. '102012'), to its decimal equivalent using first principles. -The program should consider strings specifying an invalid trinary as the -value 0. +The program should consider strings specifying an invalid trinary as the value 0. Trinary numbers contain three symbols: 0, 1, and 2. -The last place in a trinary number is the 1's place. The second to last -is the 3's place, the third to last is the 9's place, etc. +The last place in a trinary number is the 1's place. +The second to last is the 3's place, the third to last is the 9's place, etc. ```shell # "102012" @@ -18,5 +16,4 @@ is the 3's place, the third to last is the 9's place, etc. 243 + 0 + 54 + 0 + 3 + 2 = 302 ``` -If your language provides a method in the standard library to perform the -conversion, pretend it doesn't exist and implement it yourself. +If your language provides a method in the standard library to perform the conversion, pretend it doesn't exist and implement it yourself. diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md index a9bb4a3cd3..37aa75297e 100644 --- a/exercises/practice/two-fer/.docs/instructions.md +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -17,9 +17,9 @@ One for you, one for me. Here are some examples: -|Name |Dialogue -|:-------|:------------------ -|Alice |One for Alice, one for me. -|Bohdan |One for Bohdan, one for me. -| |One for you, one for me. -|Zaphod |One for Zaphod, one for me. +| Name | Dialogue | +| :----- | :-------------------------- | +| Alice | One for Alice, one for me. | +| Bohdan | One for Bohdan, one for me. | +| | One for you, one for me. | +| Zaphod | One for Zaphod, one for me. | From 7a341963902183f2deea61fa9e1727c32a6452bc Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 08:45:44 -0700 Subject: [PATCH 480/826] Remove footer macros, regen tests, all still pass (#3434) [no important files changed] --- exercises/practice/knapsack/.meta/template.j2 | 2 -- exercises/practice/knapsack/knapsack_test.py | 4 ---- exercises/practice/leap/.meta/template.j2 | 2 -- exercises/practice/leap/leap_test.py | 4 ---- exercises/practice/list-ops/.meta/template.j2 | 3 --- exercises/practice/list-ops/list_ops_test.py | 4 ---- exercises/practice/matching-brackets/.meta/template.j2 | 4 ---- .../practice/matching-brackets/matching_brackets_test.py | 4 ---- exercises/practice/pangram/.meta/template.j2 | 1 - exercises/practice/pangram/pangram_test.py | 4 ---- 10 files changed, 32 deletions(-) diff --git a/exercises/practice/knapsack/.meta/template.j2 b/exercises/practice/knapsack/.meta/template.j2 index 12760c7aba..3c59a9e89c 100644 --- a/exercises/practice/knapsack/.meta/template.j2 +++ b/exercises/practice/knapsack/.meta/template.j2 @@ -13,5 +13,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] |to_snake }}({{weight}}, {{items}}), {{expected}}) {% endif%} {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/knapsack/knapsack_test.py b/exercises/practice/knapsack/knapsack_test.py index 0a792f179c..937df1bb0f 100644 --- a/exercises/practice/knapsack/knapsack_test.py +++ b/exercises/practice/knapsack/knapsack_test.py @@ -100,7 +100,3 @@ def test_15_items(self): ), 1458, ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/leap/.meta/template.j2 b/exercises/practice/leap/.meta/template.j2 index 968306fcb7..66516c2b0d 100644 --- a/exercises/practice/leap/.meta/template.j2 +++ b/exercises/practice/leap/.meta/template.j2 @@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{- case ["expected"] }} ) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/leap/leap_test.py b/exercises/practice/leap/leap_test.py index e51aeb423a..6ba0f949b1 100644 --- a/exercises/practice/leap/leap_test.py +++ b/exercises/practice/leap/leap_test.py @@ -34,7 +34,3 @@ def test_year_divisible_by_400_but_not_by_125_is_still_a_leap_year(self): def test_year_divisible_by_200_not_divisible_by_400_in_common_year(self): self.assertIs(leap_year(1800), False) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index cc28280f17..3ce6a3033b 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -52,6 +52,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ test_case(case) }} {% endfor %} {%- endif %} - - -{{ macros.footer() }} diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index a2a301295a..cb015acc66 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -98,7 +98,3 @@ def test_foldr_foldr_add_string(self): def test_reverse_reverse_mixed_types(self): self.assertEqual(reverse(["xyz", 4.0, "cat", 1]), [1, "cat", 4.0, "xyz"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/matching-brackets/.meta/template.j2 b/exercises/practice/matching-brackets/.meta/template.j2 index bc8f083026..0d43598bfd 100644 --- a/exercises/practice/matching-brackets/.meta/template.j2 +++ b/exercises/practice/matching-brackets/.meta/template.j2 @@ -8,7 +8,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {%- set expected = case["expected"] %} self.assertEqual({{ case["property"] | to_snake }}({{ "{!r}".format(value) }}), {{ expected }}) {% endfor %} - -{{ macros.footer() }} - - diff --git a/exercises/practice/matching-brackets/matching_brackets_test.py b/exercises/practice/matching-brackets/matching_brackets_test.py index fd23bd7840..fb270ae8bc 100644 --- a/exercises/practice/matching-brackets/matching_brackets_test.py +++ b/exercises/practice/matching-brackets/matching_brackets_test.py @@ -72,7 +72,3 @@ def test_complex_latex_expression(self): ), True, ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/pangram/.meta/template.j2 b/exercises/practice/pangram/.meta/template.j2 index 7e93a44354..a365b45686 100644 --- a/exercises/practice/pangram/.meta/template.j2 +++ b/exercises/practice/pangram/.meta/template.j2 @@ -22,4 +22,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ test_case(case) }} {% endfor %} {%- endif %} -{{ macros.footer() }} diff --git a/exercises/practice/pangram/pangram_test.py b/exercises/practice/pangram/pangram_test.py index 1031cb4e6a..3f8c9aa7f3 100644 --- a/exercises/practice/pangram/pangram_test.py +++ b/exercises/practice/pangram/pangram_test.py @@ -50,7 +50,3 @@ def test_sentence_without_lower_bound(self): def test_sentence_without_upper_bound(self): self.assertIs(is_pangram("abcdefghijklmnopqrstuvwxy"), False) - - -if __name__ == "__main__": - unittest.main() From a2b8f4cebf189c88612f7d52d4464eaac553fe2d Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 09:02:04 -0700 Subject: [PATCH 481/826] Remove footer macros, regen tests, all pass still (#3432) Test File Footer Cleanup. [no important files changed] --- exercises/practice/atbash-cipher/.meta/template.j2 | 2 -- exercises/practice/atbash-cipher/atbash_cipher_test.py | 4 ---- exercises/practice/beer-song/.meta/template.j2 | 3 --- exercises/practice/beer-song/beer_song_test.py | 4 ---- exercises/practice/binary-search-tree/.meta/template.j2 | 3 --- .../practice/binary-search-tree/binary_search_tree_test.py | 4 ---- exercises/practice/bob/.meta/template.j2 | 2 -- exercises/practice/bob/bob_test.py | 4 ---- exercises/practice/book-store/.meta/template.j2 | 2 -- exercises/practice/book-store/book_store_test.py | 4 ---- 10 files changed, 32 deletions(-) diff --git a/exercises/practice/atbash-cipher/.meta/template.j2 b/exercises/practice/atbash-cipher/.meta/template.j2 index d3eb3d8762..1ad9d5759c 100644 --- a/exercises/practice/atbash-cipher/.meta/template.j2 +++ b/exercises/practice/atbash-cipher/.meta/template.j2 @@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/atbash-cipher/atbash_cipher_test.py b/exercises/practice/atbash-cipher/atbash_cipher_test.py index 98c1072afc..fcc1a25e68 100644 --- a/exercises/practice/atbash-cipher/atbash_cipher_test.py +++ b/exercises/practice/atbash-cipher/atbash_cipher_test.py @@ -61,7 +61,3 @@ def test_decode_with_no_spaces(self): self.assertEqual( decode("zmlyhgzxovrhlugvmzhgvkkrmthglmv"), "anobstacleisoftenasteppingstone" ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/beer-song/.meta/template.j2 b/exercises/practice/beer-song/.meta/template.j2 index 37e45402da..7fd4a26de7 100644 --- a/exercises/practice/beer-song/.meta/template.j2 +++ b/exercises/practice/beer-song/.meta/template.j2 @@ -18,6 +18,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} {% endfor %} - - -{{ macros.footer() }} diff --git a/exercises/practice/beer-song/beer_song_test.py b/exercises/practice/beer-song/beer_song_test.py index 4952a374c8..2aca5bae85 100644 --- a/exercises/practice/beer-song/beer_song_test.py +++ b/exercises/practice/beer-song/beer_song_test.py @@ -369,7 +369,3 @@ def test_all_verses(self): "Go to the store and buy some more, 99 bottles of beer on the wall.", ] self.assertEqual(recite(start=99, take=100), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/binary-search-tree/.meta/template.j2 b/exercises/practice/binary-search-tree/.meta/template.j2 index 518d5ceda0..e2606ddfcc 100644 --- a/exercises/practice/binary-search-tree/.meta/template.j2 +++ b/exercises/practice/binary-search-tree/.meta/template.j2 @@ -63,6 +63,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): pass else: raise AssertionError - - -{{ macros.footer() }} diff --git a/exercises/practice/binary-search-tree/binary_search_tree_test.py b/exercises/practice/binary-search-tree/binary_search_tree_test.py index 23c1fea73b..9a4a71933d 100644 --- a/exercises/practice/binary-search-tree/binary_search_tree_test.py +++ b/exercises/practice/binary-search-tree/binary_search_tree_test.py @@ -82,7 +82,3 @@ def compare_tree(self, tree_one, tree_two): pass else: raise AssertionError - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/bob/.meta/template.j2 b/exercises/practice/bob/.meta/template.j2 index ce22d1849c..07df6e8eff 100644 --- a/exercises/practice/bob/.meta/template.j2 +++ b/exercises/practice/bob/.meta/template.j2 @@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {%- set expected = case["expected"] %} self.assertEqual({{case["property"]}}("{{argument}}"), "{{expected}}") {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/bob/bob_test.py b/exercises/practice/bob/bob_test.py index 40081b045f..c033d34d8b 100644 --- a/exercises/practice/bob/bob_test.py +++ b/exercises/practice/bob/bob_test.py @@ -98,7 +98,3 @@ def test_non_question_ending_with_whitespace(self): self.assertEqual( response("This is a statement ending with whitespace "), "Whatever." ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/book-store/.meta/template.j2 b/exercises/practice/book-store/.meta/template.j2 index 6d3a79e8ff..735ecb6487 100644 --- a/exercises/practice/book-store/.meta/template.j2 +++ b/exercises/practice/book-store/.meta/template.j2 @@ -20,5 +20,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ test_case(case) }} {% endfor %} {%- endif %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/book-store/book_store_test.py b/exercises/practice/book-store/book_store_test.py index 50f38bc587..06feb75acb 100644 --- a/exercises/practice/book-store/book_store_test.py +++ b/exercises/practice/book-store/book_store_test.py @@ -95,7 +95,3 @@ def test_two_groups_of_four_and_a_group_of_five(self): def test_shuffled_book_order(self): basket = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3] self.assertEqual(total(basket), 8120) - - -if __name__ == "__main__": - unittest.main() From e076feaa4ebfa16ac1be64cd0cefc5fc9a62888c Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 09:19:02 -0700 Subject: [PATCH 482/826] Removed footer macros, regen tests, all still pass (#3431) Removed `unittest` call from test file footers. [no important files changed] --- exercises/practice/bottle-song/.meta/template.j2 | 3 --- exercises/practice/bottle-song/bottle_song_test.py | 4 ---- exercises/practice/clock/.meta/template.j2 | 1 - exercises/practice/clock/clock_test.py | 4 ---- exercises/practice/crypto-square/.meta/template.j2 | 2 -- exercises/practice/crypto-square/crypto_square_test.py | 4 ---- exercises/practice/custom-set/.meta/template.j2 | 3 +-- exercises/practice/custom-set/custom_set_test.py | 4 ---- exercises/practice/darts/.meta/template.j2 | 2 -- exercises/practice/darts/darts_test.py | 4 ---- 10 files changed, 1 insertion(+), 30 deletions(-) diff --git a/exercises/practice/bottle-song/.meta/template.j2 b/exercises/practice/bottle-song/.meta/template.j2 index 37e45402da..7fd4a26de7 100644 --- a/exercises/practice/bottle-song/.meta/template.j2 +++ b/exercises/practice/bottle-song/.meta/template.j2 @@ -18,6 +18,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} {% endfor %} - - -{{ macros.footer() }} diff --git a/exercises/practice/bottle-song/bottle_song_test.py b/exercises/practice/bottle-song/bottle_song_test.py index 89877dc494..3c3108f929 100644 --- a/exercises/practice/bottle-song/bottle_song_test.py +++ b/exercises/practice/bottle-song/bottle_song_test.py @@ -130,7 +130,3 @@ def test_all_verses(self): "There'll be no green bottles hanging on the wall.", ] self.assertEqual(recite(start=10, take=10), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/clock/.meta/template.j2 b/exercises/practice/clock/.meta/template.j2 index f786b2f1c8..c8cee7e08d 100644 --- a/exercises/practice/clock/.meta/template.j2 +++ b/exercises/practice/clock/.meta/template.j2 @@ -29,4 +29,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {%- endfor %} -{{ macros.footer() }} diff --git a/exercises/practice/clock/clock_test.py b/exercises/practice/clock/clock_test.py index fe06336676..b2c6fe9d89 100644 --- a/exercises/practice/clock/clock_test.py +++ b/exercises/practice/clock/clock_test.py @@ -177,7 +177,3 @@ def test_clocks_with_negative_hours_and_minutes_that_wrap(self): def test_full_clock_and_zeroed_clock(self): self.assertEqual(Clock(24, 0), Clock(0, 0)) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/crypto-square/.meta/template.j2 b/exercises/practice/crypto-square/.meta/template.j2 index 5568ca0913..c33812e64c 100644 --- a/exercises/practice/crypto-square/.meta/template.j2 +++ b/exercises/practice/crypto-square/.meta/template.j2 @@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual(cipher_text(value), expected) {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/crypto-square/crypto_square_test.py b/exercises/practice/crypto-square/crypto_square_test.py index 3715400ca3..553728f3fe 100644 --- a/exercises/practice/crypto-square/crypto_square_test.py +++ b/exercises/practice/crypto-square/crypto_square_test.py @@ -51,7 +51,3 @@ def test_54_character_plaintext_results_in_7_chunks_the_last_two_with_trailing_s value = "If man was meant to stay on the ground, god would have given us roots." expected = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " self.assertEqual(cipher_text(value), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/custom-set/.meta/template.j2 b/exercises/practice/custom-set/.meta/template.j2 index 1341226594..d70a45c298 100644 --- a/exercises/practice/custom-set/.meta/template.j2 +++ b/exercises/practice/custom-set/.meta/template.j2 @@ -44,5 +44,4 @@ class {{ class_name }}Test(unittest.TestCase): {% endif %} {% endfor %} {% endfor %} - -{{ macros.footer() }} + \ No newline at end of file diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index e5e3ffd3bc..30c945ac73 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -212,7 +212,3 @@ def test_union_of_non_empty_sets_contains_all_unique_elements(self): set2 = CustomSet([2, 3]) expected = CustomSet([3, 2, 1]) self.assertEqual(set1 + set2, expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/darts/.meta/template.j2 b/exercises/practice/darts/.meta/template.j2 index 09c0361b2f..35a5c37dd3 100644 --- a/exercises/practice/darts/.meta/template.j2 +++ b/exercises/practice/darts/.meta/template.j2 @@ -6,5 +6,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): def test_{{ case["description"] | to_snake }}(self): self.assertEqual({{ case["property"] }}({{ case["input"]["x"] }}, {{ case["input"]["y"] }}), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/darts/darts_test.py b/exercises/practice/darts/darts_test.py index e830199c9c..94a9c5ed3c 100644 --- a/exercises/practice/darts/darts_test.py +++ b/exercises/practice/darts/darts_test.py @@ -46,7 +46,3 @@ def test_just_outside_the_outer_circle(self): def test_asymmetric_position_between_the_inner_and_middle_circles(self): self.assertEqual(score(0.5, -4), 5) - - -if __name__ == "__main__": - unittest.main() From 4ce02b7fd8aeffdda7e037a82e39742e0acd3245 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Jul 2023 09:28:29 -0700 Subject: [PATCH 483/826] Removed macro footer from test file template and regenerated. (#3458) Removed `unittest` main footer. [no important files changed] --- exercises/practice/rna-transcription/.meta/template.j2 | 2 -- .../practice/rna-transcription/rna_transcription_test.py | 4 ---- 2 files changed, 6 deletions(-) diff --git a/exercises/practice/rna-transcription/.meta/template.j2 b/exercises/practice/rna-transcription/.meta/template.j2 index 6be1210ad6..ebd1675187 100644 --- a/exercises/practice/rna-transcription/.meta/template.j2 +++ b/exercises/practice/rna-transcription/.meta/template.j2 @@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): '{{ case["expected"] }}' ) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/rna-transcription/rna_transcription_test.py b/exercises/practice/rna-transcription/rna_transcription_test.py index aa06232959..e6c88062bf 100644 --- a/exercises/practice/rna-transcription/rna_transcription_test.py +++ b/exercises/practice/rna-transcription/rna_transcription_test.py @@ -25,7 +25,3 @@ def test_rna_complement_of_adenine_is_uracil(self): def test_rna_complement(self): self.assertEqual(to_rna("ACGTGGTCTTAA"), "UGCACCAGAAUU") - - -if __name__ == "__main__": - unittest.main() From 425e25b8fb7ac28f8b5397ca2b8f39fcf95cfab8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Jul 2023 09:41:06 -0700 Subject: [PATCH 484/826] [Ledger]: Added JinJa2 Template, Re-Synced Tests & Regenerated Test Files (#3463) * Added JinJa template, tests_toml and regenerated tests. * Added names to contributors list. [no important files changed] --- exercises/practice/ledger/.meta/config.json | 2 + exercises/practice/ledger/.meta/template.j2 | 22 +++ exercises/practice/ledger/.meta/tests.toml | 43 +++++ exercises/practice/ledger/ledger.py | 1 + exercises/practice/ledger/ledger_test.py | 200 +++++++++++--------- 5 files changed, 180 insertions(+), 88 deletions(-) create mode 100644 exercises/practice/ledger/.meta/template.j2 create mode 100644 exercises/practice/ledger/.meta/tests.toml diff --git a/exercises/practice/ledger/.meta/config.json b/exercises/practice/ledger/.meta/config.json index eeb5e2ccdf..5b9998ba1b 100644 --- a/exercises/practice/ledger/.meta/config.json +++ b/exercises/practice/ledger/.meta/config.json @@ -3,7 +3,9 @@ "cmccandless" ], "contributors": [ + "BethanyG", "Dog", + "IsaaG", "tqa236" ], "files": { diff --git a/exercises/practice/ledger/.meta/template.j2 b/exercises/practice/ledger/.meta/template.j2 new file mode 100644 index 0000000000..8936a2ca9a --- /dev/null +++ b/exercises/practice/ledger/.meta/template.j2 @@ -0,0 +1,22 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.header(imports=["format_entries", "create_entry"]) }} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + maxDiff = 5000 + {%- for case in cases %} + + def test_{{ case["description"] | to_snake }}(self): + currency = '{{- case["input"]["currency"] }}' + locale = '{{- case["input"]["locale"] }}' + entries = [ + {%- for entry in case["input"]["entries"] %} + create_entry('{{ entry["date"] }}', '{{ entry["description"] }}', {{ entry["amountInCents"] }}), + {%- endfor %} + ] + expected = '\n'.join([ + {%- for line in case["expected"] %} + '{{- line -}}', + {%- endfor %} + ]) + self.assertEqual(format_entries(currency, locale, entries), expected) + {%- endfor %} diff --git a/exercises/practice/ledger/.meta/tests.toml b/exercises/practice/ledger/.meta/tests.toml new file mode 100644 index 0000000000..e71dfbfcaf --- /dev/null +++ b/exercises/practice/ledger/.meta/tests.toml @@ -0,0 +1,43 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d131ecae-a30e-436c-b8f3-858039a27234] +description = "empty ledger" + +[ce4618d2-9379-4eca-b207-9df1c4ec8aaa] +description = "one entry" + +[8d02e9cb-e6ee-4b77-9ce4-e5aec8eb5ccb] +description = "credit and debit" + +[502c4106-0371-4e7c-a7d8-9ce33f16ccb1] +description = "multiple entries on same date ordered by description" + +[29dd3659-6c2d-4380-94a8-6d96086e28e1] +description = "final order tie breaker is change" + +[9b9712a6-f779-4f5c-a759-af65615fcbb9] +description = "overlong description is truncated" + +[67318aad-af53-4f3d-aa19-1293b4d4c924] +description = "euros" + +[bdc499b6-51f5-4117-95f2-43cb6737208e] +description = "Dutch locale" + +[86591cd4-1379-4208-ae54-0ee2652b4670] +description = "Dutch locale and euros" + +[876bcec8-d7d7-4ba4-82bd-b836ac87c5d2] +description = "Dutch negative number with 3 digits before decimal point" + +[29670d1c-56be-492a-9c5e-427e4b766309] +description = "American negative number with 3 digits before decimal point" diff --git a/exercises/practice/ledger/ledger.py b/exercises/practice/ledger/ledger.py index 493109b847..c0dcf94f73 100644 --- a/exercises/practice/ledger/ledger.py +++ b/exercises/practice/ledger/ledger.py @@ -296,3 +296,4 @@ def format_entries(currency, locale, entries): change_str = ' ' + change_str table += change_str return table + diff --git a/exercises/practice/ledger/ledger_test.py b/exercises/practice/ledger/ledger_test.py index f0de049df1..c1615666ce 100644 --- a/exercises/practice/ledger/ledger_test.py +++ b/exercises/practice/ledger/ledger_test.py @@ -1,147 +1,171 @@ -# -*- coding: utf-8 -*- import unittest -from ledger import format_entries, create_entry +from ledger import ( + format_entries, + create_entry, +) + +# Tests adapted from `problem-specifications//canonical-data.json` class LedgerTest(unittest.TestCase): maxDiff = 5000 def test_empty_ledger(self): - currency = 'USD' - locale = 'en_US' + currency = "USD" + locale = "en_US" entries = [] - expected = 'Date | Description | Change ' + expected = "\n".join( + [ + "Date | Description | Change ", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_one_entry(self): - currency = 'USD' - locale = 'en_US' + currency = "USD" + locale = "en_US" entries = [ - create_entry('2015-01-01', 'Buy present', -1000), + create_entry("2015-01-01", "Buy present", -1000), ] - expected = '\n'.join([ - 'Date | Description | Change ', - '01/01/2015 | Buy present | ($10.00)', - ]) + expected = "\n".join( + [ + "Date | Description | Change ", + "01/01/2015 | Buy present | ($10.00)", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_credit_and_debit(self): - currency = 'USD' - locale = 'en_US' + currency = "USD" + locale = "en_US" entries = [ - create_entry('2015-01-02', 'Get present', 1000), - create_entry('2015-01-01', 'Buy present', -1000), + create_entry("2015-01-02", "Get present", 1000), + create_entry("2015-01-01", "Buy present", -1000), ] - expected = '\n'.join([ - 'Date | Description | Change ', - '01/01/2015 | Buy present | ($10.00)', - '01/02/2015 | Get present | $10.00 ', - ]) + expected = "\n".join( + [ + "Date | Description | Change ", + "01/01/2015 | Buy present | ($10.00)", + "01/02/2015 | Get present | $10.00 ", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_multiple_entries_on_same_date_ordered_by_description(self): - currency = 'USD' - locale = 'en_US' + currency = "USD" + locale = "en_US" entries = [ - create_entry('2015-01-02', 'Get present', 1000), - create_entry('2015-01-01', 'Buy present', -1000), + create_entry("2015-01-02", "Get present", 1000), + create_entry("2015-01-01", "Buy present", -1000), ] - expected = '\n'.join([ - 'Date | Description | Change ', - '01/01/2015 | Buy present | ($10.00)', - '01/02/2015 | Get present | $10.00 ', - ]) + expected = "\n".join( + [ + "Date | Description | Change ", + "01/01/2015 | Buy present | ($10.00)", + "01/02/2015 | Get present | $10.00 ", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_final_order_tie_breaker_is_change(self): - currency = 'USD' - locale = 'en_US' + currency = "USD" + locale = "en_US" entries = [ - create_entry('2015-01-01', 'Something', 0), - create_entry('2015-01-01', 'Something', -1), - create_entry('2015-01-01', 'Something', 1), + create_entry("2015-01-01", "Something", 0), + create_entry("2015-01-01", "Something", -1), + create_entry("2015-01-01", "Something", 1), ] - expected = '\n'.join([ - 'Date | Description | Change ', - '01/01/2015 | Something | ($0.01)', - '01/01/2015 | Something | $0.00 ', - '01/01/2015 | Something | $0.01 ', - ]) + expected = "\n".join( + [ + "Date | Description | Change ", + "01/01/2015 | Something | ($0.01)", + "01/01/2015 | Something | $0.00 ", + "01/01/2015 | Something | $0.01 ", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) - def test_overlong_description(self): - currency = 'USD' - locale = 'en_US' + def test_overlong_description_is_truncated(self): + currency = "USD" + locale = "en_US" entries = [ - create_entry('2015-01-01', 'Freude schoner Gotterfunken', -123456), + create_entry("2015-01-01", "Freude schoner Gotterfunken", -123456), ] - expected = '\n'.join([ - 'Date | Description | Change ', - '01/01/2015 | Freude schoner Gotterf... | ($1,234.56)', - ]) + expected = "\n".join( + [ + "Date | Description | Change ", + "01/01/2015 | Freude schoner Gotterf... | ($1,234.56)", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_euros(self): - currency = 'EUR' - locale = 'en_US' + currency = "EUR" + locale = "en_US" entries = [ - create_entry('2015-01-01', 'Buy present', -1000), + create_entry("2015-01-01", "Buy present", -1000), ] - expected = '\n'.join([ - 'Date | Description | Change ', - u'01/01/2015 | Buy present | (€10.00)', - ]) + expected = "\n".join( + [ + "Date | Description | Change ", + "01/01/2015 | Buy present | (€10.00)", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_dutch_locale(self): - currency = 'USD' - locale = 'nl_NL' + currency = "USD" + locale = "nl_NL" entries = [ - create_entry('2015-03-12', 'Buy present', 123456), + create_entry("2015-03-12", "Buy present", 123456), ] - expected = '\n'.join([ - 'Datum | Omschrijving | Verandering ', - '12-03-2015 | Buy present | $ 1.234,56 ', - ]) + expected = "\n".join( + [ + "Datum | Omschrijving | Verandering ", + "12-03-2015 | Buy present | $ 1.234,56 ", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_dutch_locale_and_euros(self): - currency = 'EUR' - locale = 'nl_NL' + currency = "EUR" + locale = "nl_NL" entries = [ - create_entry('2015-03-12', 'Buy present', 123456), + create_entry("2015-03-12", "Buy present", 123456), ] - expected = '\n'.join([ - 'Datum | Omschrijving | Verandering ', - u'12-03-2015 | Buy present | € 1.234,56 ', - ]) + expected = "\n".join( + [ + "Datum | Omschrijving | Verandering ", + "12-03-2015 | Buy present | € 1.234,56 ", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_dutch_negative_number_with_3_digits_before_decimal_point(self): - currency = 'USD' - locale = 'nl_NL' + currency = "USD" + locale = "nl_NL" entries = [ - create_entry('2015-03-12', 'Buy present', -12345), + create_entry("2015-03-12", "Buy present", -12345), ] - expected = '\n'.join([ - 'Datum | Omschrijving | Verandering ', - '12-03-2015 | Buy present | $ -123,45 ', - ]) + expected = "\n".join( + [ + "Datum | Omschrijving | Verandering ", + "12-03-2015 | Buy present | $ -123,45 ", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) def test_american_negative_number_with_3_digits_before_decimal_point(self): - currency = 'USD' - locale = 'en_US' + currency = "USD" + locale = "en_US" entries = [ - create_entry('2015-03-12', 'Buy present', -12345), + create_entry("2015-03-12", "Buy present", -12345), ] - expected = '\n'.join([ - 'Date | Description | Change ', - '03/12/2015 | Buy present | ($123.45)', - ]) + expected = "\n".join( + [ + "Date | Description | Change ", + "03/12/2015 | Buy present | ($123.45)", + ] + ) self.assertEqual(format_entries(currency, locale, entries), expected) - - -if __name__ == '__main__': - unittest.main() From 1e56253ed254f4c09389654139e257f21b7ca718 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Jul 2023 09:57:36 -0700 Subject: [PATCH 485/826] Synced instructions for two depricated exercises. (#3459) From 7ef38f44be1f1e0127c469e722e4a9306bacfa3b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Jul 2023 09:57:50 -0700 Subject: [PATCH 486/826] Synced instructions for three active exercises. (#3460) From 37feda4d82397130ed1526a82cdc7cbf5d3e4efb Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 11:29:50 -0700 Subject: [PATCH 487/826] Remove footer macros, regen tests, all pass still (#3433) [no important files changed] --- exercises/practice/acronym/.meta/template.j2 | 1 - exercises/practice/acronym/acronym_test.py | 4 ---- exercises/practice/all-your-base/.meta/template.j2 | 1 - exercises/practice/all-your-base/all_your_base_test.py | 8 -------- exercises/practice/allergies/.meta/template.j2 | 2 -- exercises/practice/allergies/allergies_test.py | 4 ---- exercises/practice/alphametics/.meta/template.j2 | 1 - exercises/practice/alphametics/alphametics_test.py | 4 ---- exercises/practice/armstrong-numbers/.meta/template.j2 | 4 +--- .../practice/armstrong-numbers/armstrong_numbers_test.py | 4 ---- 10 files changed, 1 insertion(+), 32 deletions(-) diff --git a/exercises/practice/acronym/.meta/template.j2 b/exercises/practice/acronym/.meta/template.j2 index 1373d724d3..70c5d431ca 100644 --- a/exercises/practice/acronym/.meta/template.j2 +++ b/exercises/practice/acronym/.meta/template.j2 @@ -13,4 +13,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} {{ test_case(case) }} {% endfor %} -{{ macros.footer() }} diff --git a/exercises/practice/acronym/acronym_test.py b/exercises/practice/acronym/acronym_test.py index c5fbbfa39e..19e4e48662 100644 --- a/exercises/practice/acronym/acronym_test.py +++ b/exercises/practice/acronym/acronym_test.py @@ -39,7 +39,3 @@ def test_apostrophes(self): def test_underscore_emphasis(self): self.assertEqual(abbreviate("The Road _Not_ Taken"), "TRNT") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/all-your-base/.meta/template.j2 b/exercises/practice/all-your-base/.meta/template.j2 index 20684b546c..b19efb5b8b 100644 --- a/exercises/practice/all-your-base/.meta/template.j2 +++ b/exercises/practice/all-your-base/.meta/template.j2 @@ -29,4 +29,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ test_case(case) }} {% endfor -%} -{{ macros.footer() }} diff --git a/exercises/practice/all-your-base/all_your_base_test.py b/exercises/practice/all-your-base/all_your_base_test.py index 04cf27dbae..0191754acb 100644 --- a/exercises/practice/all-your-base/all_your_base_test.py +++ b/exercises/practice/all-your-base/all_your_base_test.py @@ -101,11 +101,3 @@ def test_both_bases_are_negative(self): rebase(-2, [1], -7) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "input base must be >= 2") - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/allergies/.meta/template.j2 b/exercises/practice/allergies/.meta/template.j2 index f39a9d54a4..a15dc17639 100644 --- a/exercises/practice/allergies/.meta/template.j2 +++ b/exercises/practice/allergies/.meta/template.j2 @@ -27,5 +27,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/allergies/allergies_test.py b/exercises/practice/allergies/allergies_test.py index 5b8d32c085..4525db8edb 100644 --- a/exercises/practice/allergies/allergies_test.py +++ b/exercises/practice/allergies/allergies_test.py @@ -183,7 +183,3 @@ def test_no_allergen_score_parts(self): def test_no_allergen_score_parts_without_highest_valid_score(self): self.assertEqual(Allergies(257).lst, ["eggs"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/alphametics/.meta/template.j2 b/exercises/practice/alphametics/.meta/template.j2 index 4841b41ee5..73291b8a1e 100644 --- a/exercises/practice/alphametics/.meta/template.j2 +++ b/exercises/practice/alphametics/.meta/template.j2 @@ -30,4 +30,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endif %} {% endfor %} -{{ macros.footer()}} diff --git a/exercises/practice/alphametics/alphametics_test.py b/exercises/practice/alphametics/alphametics_test.py index c1f573391b..8853e1e71c 100644 --- a/exercises/practice/alphametics/alphametics_test.py +++ b/exercises/practice/alphametics/alphametics_test.py @@ -106,7 +106,3 @@ def test_puzzle_with_ten_letters_and_199_addends(self): "T": 9, }, ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/armstrong-numbers/.meta/template.j2 b/exercises/practice/armstrong-numbers/.meta/template.j2 index f6ac6e765e..d9a1cceb6d 100644 --- a/exercises/practice/armstrong-numbers/.meta/template.j2 +++ b/exercises/practice/armstrong-numbers/.meta/template.j2 @@ -9,6 +9,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ case["expected"] }} ) - {% endfor %} - -{{ macros.footer() }} \ No newline at end of file + {% endfor %} \ No newline at end of file diff --git a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py index c3f6089e9c..6a4bd96e83 100644 --- a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py +++ b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py @@ -34,7 +34,3 @@ def test_seven_digit_number_that_is_an_armstrong_number(self): def test_seven_digit_number_that_is_not_an_armstrong_number(self): self.assertIs(is_armstrong_number(9926314), False) - - -if __name__ == "__main__": - unittest.main() From bdc5fe9986e208985e3a127a963e541e3f7a8045 Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 11:44:32 -0700 Subject: [PATCH 488/826] Remvoed footer macros, regen tests, all still pass (#3430) [no important files changed] --- exercises/practice/diamond/.meta/template.j2 | 2 -- exercises/practice/diamond/diamond_test.py | 4 ---- exercises/practice/difference-of-squares/.meta/template.j2 | 2 -- .../difference-of-squares/difference_of_squares_test.py | 4 ---- exercises/practice/dominoes/.meta/template.j2 | 3 --- exercises/practice/dominoes/dominoes_test.py | 4 ---- exercises/practice/etl/.meta/template.j2 | 3 --- exercises/practice/etl/etl_test.py | 4 ---- exercises/practice/flatten-array/.meta/template.j2 | 3 +-- exercises/practice/flatten-array/flatten_array_test.py | 4 ---- 10 files changed, 1 insertion(+), 32 deletions(-) diff --git a/exercises/practice/diamond/.meta/template.j2 b/exercises/practice/diamond/.meta/template.j2 index e9357cc7ac..5651fa6bc9 100644 --- a/exercises/practice/diamond/.meta/template.j2 +++ b/exercises/practice/diamond/.meta/template.j2 @@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): result = {{expected}} self.assertEqual({{case["property"]}}("{{letter}}"), result) {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/diamond/diamond_test.py b/exercises/practice/diamond/diamond_test.py index f0e60c862d..9ed44ee198 100644 --- a/exercises/practice/diamond/diamond_test.py +++ b/exercises/practice/diamond/diamond_test.py @@ -87,7 +87,3 @@ def test_largest_possible_diamond(self): " A ", ] self.assertEqual(rows("Z"), result) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/difference-of-squares/.meta/template.j2 b/exercises/practice/difference-of-squares/.meta/template.j2 index 62b8d8d218..ffac71e284 100644 --- a/exercises/practice/difference-of-squares/.meta/template.j2 +++ b/exercises/practice/difference-of-squares/.meta/template.j2 @@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/difference-of-squares/difference_of_squares_test.py b/exercises/practice/difference-of-squares/difference_of_squares_test.py index 8c2069e4d9..fa39f03e55 100644 --- a/exercises/practice/difference-of-squares/difference_of_squares_test.py +++ b/exercises/practice/difference-of-squares/difference_of_squares_test.py @@ -36,7 +36,3 @@ def test_difference_of_squares_5(self): def test_difference_of_squares_100(self): self.assertEqual(difference_of_squares(100), 25164150) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/dominoes/.meta/template.j2 b/exercises/practice/dominoes/.meta/template.j2 index e75094e990..3ec60875d0 100644 --- a/exercises/practice/dominoes/.meta/template.j2 +++ b/exercises/practice/dominoes/.meta/template.j2 @@ -64,6 +64,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): def refute_correct_chain(self, input_dominoes, output_chain): msg = 'There should be no valid chain for {}'.format(input_dominoes) self.assertIsNone(output_chain, msg) - -{{ macros.footer() }} - diff --git a/exercises/practice/dominoes/dominoes_test.py b/exercises/practice/dominoes/dominoes_test.py index a62f130cc2..a9b8c90dd8 100644 --- a/exercises/practice/dominoes/dominoes_test.py +++ b/exercises/practice/dominoes/dominoes_test.py @@ -128,7 +128,3 @@ def assert_correct_chain(self, input_dominoes, output_chain): def refute_correct_chain(self, input_dominoes, output_chain): msg = "There should be no valid chain for {}".format(input_dominoes) self.assertIsNone(output_chain, msg) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/etl/.meta/template.j2 b/exercises/practice/etl/.meta/template.j2 index 8e616d8874..5669b6ed99 100644 --- a/exercises/practice/etl/.meta/template.j2 +++ b/exercises/practice/etl/.meta/template.j2 @@ -15,6 +15,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} {{ test_case(case) }} {% endfor %} - - -{{ macros.footer() }} diff --git a/exercises/practice/etl/etl_test.py b/exercises/practice/etl/etl_test.py index 693ecb9e86..fbd57fd289 100644 --- a/exercises/practice/etl/etl_test.py +++ b/exercises/practice/etl/etl_test.py @@ -62,7 +62,3 @@ def test_multiple_scores_with_differing_numbers_of_letters(self): "z": 10, } self.assertEqual(transform(legacy_data), data) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/flatten-array/.meta/template.j2 b/exercises/practice/flatten-array/.meta/template.j2 index d1d93ad711..425e92fee9 100644 --- a/exercises/practice/flatten-array/.meta/template.j2 +++ b/exercises/practice/flatten-array/.meta/template.j2 @@ -9,5 +9,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] }}(inputs), expected) {% endfor %} - -{{ macros.footer() }} + \ No newline at end of file diff --git a/exercises/practice/flatten-array/flatten_array_test.py b/exercises/practice/flatten-array/flatten_array_test.py index 1552f0edb6..3038d1a00f 100644 --- a/exercises/practice/flatten-array/flatten_array_test.py +++ b/exercises/practice/flatten-array/flatten_array_test.py @@ -66,7 +66,3 @@ def test_all_values_in_nested_list_are_null(self): inputs = [None, [[[None]]], None, None, [[None, None], None], None] expected = [] self.assertEqual(flatten(inputs), expected) - - -if __name__ == "__main__": - unittest.main() From feb9cf1da8aa0db92cbfc1ec45b58a708be73627 Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 11:54:43 -0700 Subject: [PATCH 489/826] Removed footer macros, regen tests all still pass (#3429) [no important files changed] --- exercises/practice/food-chain/.meta/template.j2 | 2 -- exercises/practice/food-chain/food_chain_test.py | 4 ---- exercises/practice/gigasecond/.meta/template.j2 | 3 --- exercises/practice/gigasecond/gigasecond_test.py | 4 ---- exercises/practice/grep/.meta/template.j2 | 2 -- exercises/practice/grep/grep_test.py | 4 ---- exercises/practice/house/.meta/template.j2 | 2 -- exercises/practice/house/house_test.py | 4 ---- exercises/practice/isbn-verifier/.meta/template.j2 | 2 -- exercises/practice/isbn-verifier/isbn_verifier_test.py | 4 ---- 10 files changed, 31 deletions(-) diff --git a/exercises/practice/food-chain/.meta/template.j2 b/exercises/practice/food-chain/.meta/template.j2 index e1851f7a30..619ada74c2 100644 --- a/exercises/practice/food-chain/.meta/template.j2 +++ b/exercises/practice/food-chain/.meta/template.j2 @@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"]}}({{start_verse}}, {{end_verse}}), {{expected}}) {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/food-chain/food_chain_test.py b/exercises/practice/food-chain/food_chain_test.py index d77953527e..f87f4cc9a2 100644 --- a/exercises/practice/food-chain/food_chain_test.py +++ b/exercises/practice/food-chain/food_chain_test.py @@ -180,7 +180,3 @@ def test_full_song(self): "She's dead, of course!", ], ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/gigasecond/.meta/template.j2 b/exercises/practice/gigasecond/.meta/template.j2 index 31d032a46f..87b0a14843 100644 --- a/exercises/practice/gigasecond/.meta/template.j2 +++ b/exercises/practice/gigasecond/.meta/template.j2 @@ -9,6 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): def test_{{ case["description"] | to_snake }}(self): self.assertEqual({{ case["property"] }}({{ input }}), {{ expected }}) {% endfor %} - - -{{ macros.footer() }} diff --git a/exercises/practice/gigasecond/gigasecond_test.py b/exercises/practice/gigasecond/gigasecond_test.py index 76ff16a64c..e7b5f0837f 100644 --- a/exercises/practice/gigasecond/gigasecond_test.py +++ b/exercises/practice/gigasecond/gigasecond_test.py @@ -33,7 +33,3 @@ def test_full_time_with_day_roll_over(self): self.assertEqual( add(datetime(2015, 1, 24, 23, 59, 59)), datetime(2046, 10, 3, 1, 46, 39) ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/grep/.meta/template.j2 b/exercises/practice/grep/.meta/template.j2 index 6091fd667e..593bbc0781 100644 --- a/exercises/practice/grep/.meta/template.j2 +++ b/exercises/practice/grep/.meta/template.j2 @@ -48,5 +48,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/grep/grep_test.py b/exercises/practice/grep/grep_test.py index 804071542d..87c76ac829 100644 --- a/exercises/practice/grep/grep_test.py +++ b/exercises/practice/grep/grep_test.py @@ -290,7 +290,3 @@ def test_multiple_files_several_matches_inverted_and_match_entire_lines_flags( "paradise-lost.txt:Of Oreb, or of Sinai, didst inspire\n" "paradise-lost.txt:That Shepherd, who first taught the chosen Seed\n", ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/house/.meta/template.j2 b/exercises/practice/house/.meta/template.j2 index 9683c07a99..b855685dfd 100644 --- a/exercises/practice/house/.meta/template.j2 +++ b/exercises/practice/house/.meta/template.j2 @@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ case["expected"] }} ) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/house/house_test.py b/exercises/practice/house/house_test.py index 0facef614f..8247675823 100644 --- a/exercises/practice/house/house_test.py +++ b/exercises/practice/house/house_test.py @@ -126,7 +126,3 @@ def test_full_rhyme(self): "This is the horse and the hound and the horn that belonged to the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.", ], ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/isbn-verifier/.meta/template.j2 b/exercises/practice/isbn-verifier/.meta/template.j2 index ce973c491d..47392cfb29 100644 --- a/exercises/practice/isbn-verifier/.meta/template.j2 +++ b/exercises/practice/isbn-verifier/.meta/template.j2 @@ -7,5 +7,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): def test_{{ case["description"] | to_snake }}(self): self.assertIs({{ case["property"] | to_snake }}("{{ case["input"]["isbn"] }}"), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/isbn-verifier/isbn_verifier_test.py b/exercises/practice/isbn-verifier/isbn_verifier_test.py index f2f97770d7..22fd28922f 100644 --- a/exercises/practice/isbn-verifier/isbn_verifier_test.py +++ b/exercises/practice/isbn-verifier/isbn_verifier_test.py @@ -64,7 +64,3 @@ def test_invalid_characters_are_not_ignored_before_checking_length(self): def test_input_is_too_long_but_contains_a_valid_isbn(self): self.assertIs(is_valid("98245726788"), False) - - -if __name__ == "__main__": - unittest.main() From 0075a6e16c9199bb4b93997e8ca2fb94b5f733cb Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 12:09:03 -0700 Subject: [PATCH 490/826] Remvoed footer macros, regen tests, all still pass (#3428) [no important files changed] --- exercises/practice/pig-latin/.meta/template.j2 | 2 -- exercises/practice/pig-latin/pig_latin_test.py | 4 ---- exercises/practice/poker/.meta/template.j2 | 2 -- exercises/practice/poker/poker_test.py | 4 ---- exercises/practice/protein-translation/.meta/template.j2 | 3 +-- .../practice/protein-translation/protein_translation_test.py | 4 ---- exercises/practice/pythagorean-triplet/.meta/template.j2 | 2 -- .../practice/pythagorean-triplet/pythagorean_triplet_test.py | 4 ---- exercises/practice/rail-fence-cipher/.meta/template.j2 | 2 -- .../practice/rail-fence-cipher/rail_fence_cipher_test.py | 4 ---- 10 files changed, 1 insertion(+), 30 deletions(-) diff --git a/exercises/practice/pig-latin/.meta/template.j2 b/exercises/practice/pig-latin/.meta/template.j2 index f516f6f1a8..fd284824ff 100644 --- a/exercises/practice/pig-latin/.meta/template.j2 +++ b/exercises/practice/pig-latin/.meta/template.j2 @@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/pig-latin/pig_latin_test.py b/exercises/practice/pig-latin/pig_latin_test.py index cf666ddf3a..cf46ae3c1f 100644 --- a/exercises/practice/pig-latin/pig_latin_test.py +++ b/exercises/practice/pig-latin/pig_latin_test.py @@ -73,7 +73,3 @@ def test_y_as_second_letter_in_two_letter_word(self): def test_a_whole_phrase(self): self.assertEqual(translate("quick fast run"), "ickquay astfay unray") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/poker/.meta/template.j2 b/exercises/practice/poker/.meta/template.j2 index 099626a134..86e2e8791a 100644 --- a/exercises/practice/poker/.meta/template.j2 +++ b/exercises/practice/poker/.meta/template.j2 @@ -8,5 +8,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual( {{ case["property"] | to_snake }}({{ input["hands"] }}), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py index f436e48120..d0b2c52dc0 100644 --- a/exercises/practice/poker/poker_test.py +++ b/exercises/practice/poker/poker_test.py @@ -209,7 +209,3 @@ def test_even_though_an_ace_is_usually_high_a_5_high_straight_flush_is_the_lowes self.assertEqual( best_hands(["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"]), ["2H 3H 4H 5H 6H"] ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/protein-translation/.meta/template.j2 b/exercises/practice/protein-translation/.meta/template.j2 index 5cc931bcfd..a8391c37ff 100644 --- a/exercises/practice/protein-translation/.meta/template.j2 +++ b/exercises/practice/protein-translation/.meta/template.j2 @@ -9,5 +9,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] }}(value), expected) {% endfor %} - -{{ macros.footer() }} + \ No newline at end of file diff --git a/exercises/practice/protein-translation/protein_translation_test.py b/exercises/practice/protein-translation/protein_translation_test.py index 046c0a43e7..4ce2d1151b 100644 --- a/exercises/practice/protein-translation/protein_translation_test.py +++ b/exercises/practice/protein-translation/protein_translation_test.py @@ -132,7 +132,3 @@ def test_translation_stops_if_stop_codon_in_middle_of_six_codon_sequence(self): value = "UGGUGUUAUUAAUGGUUU" expected = ["Tryptophan", "Cysteine", "Tyrosine"] self.assertEqual(proteins(value), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/pythagorean-triplet/.meta/template.j2 b/exercises/practice/pythagorean-triplet/.meta/template.j2 index e52cda4e23..20d750c774 100644 --- a/exercises/practice/pythagorean-triplet/.meta/template.j2 +++ b/exercises/practice/pythagorean-triplet/.meta/template.j2 @@ -8,5 +8,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): def test_{{ case["description"] | to_snake }}(self): self.assertCountEqual({{ case["property"] | to_snake }}({{ case["input"]["n"] }}), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py index bb094c909d..3536908ae8 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py +++ b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py @@ -49,7 +49,3 @@ def test_triplets_for_large_number(self): [7500, 10000, 12500], ], ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/rail-fence-cipher/.meta/template.j2 b/exercises/practice/rail-fence-cipher/.meta/template.j2 index 2c11e606c6..2698af337c 100644 --- a/exercises/practice/rail-fence-cipher/.meta/template.j2 +++ b/exercises/practice/rail-fence-cipher/.meta/template.j2 @@ -12,5 +12,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py index 65743180a7..873de5839b 100644 --- a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py +++ b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py @@ -33,7 +33,3 @@ def test_decode_with_six_rails(self): decode("133714114238148966225439541018335470986172518171757571896261", 6), "112358132134558914423337761098715972584418167651094617711286", ) - - -if __name__ == "__main__": - unittest.main() From c594bbf55b218ae8a840a1a8ceac4a2cfdd1e130 Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 12:21:22 -0700 Subject: [PATCH 491/826] Removed footer macros, regen tests, all still pass (#3427) [no important files changed] --- exercises/practice/raindrops/.meta/template.j2 | 2 -- exercises/practice/raindrops/raindrops_test.py | 4 ---- exercises/practice/rectangles/.meta/template.j2 | 3 +-- exercises/practice/rectangles/rectangles_test.py | 4 ---- exercises/practice/resistor-color/.meta/template.j2 | 2 -- exercises/practice/resistor-color/resistor_color_test.py | 4 ---- exercises/practice/rest-api/.meta/template.j2 | 2 -- exercises/practice/rest-api/rest_api_test.py | 4 ---- exercises/practice/reverse-string/.meta/template.j2 | 2 -- exercises/practice/reverse-string/reverse_string_test.py | 4 ---- 10 files changed, 1 insertion(+), 30 deletions(-) diff --git a/exercises/practice/raindrops/.meta/template.j2 b/exercises/practice/raindrops/.meta/template.j2 index d1b0d2f861..1e025b8bf1 100644 --- a/exercises/practice/raindrops/.meta/template.j2 +++ b/exercises/practice/raindrops/.meta/template.j2 @@ -16,5 +16,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): "{{ case["expected"] }}" ) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/raindrops/raindrops_test.py b/exercises/practice/raindrops/raindrops_test.py index 247f4daa47..7a5969c943 100644 --- a/exercises/practice/raindrops/raindrops_test.py +++ b/exercises/practice/raindrops/raindrops_test.py @@ -63,7 +63,3 @@ def test_the_sound_for_105_is_pling_plang_plong_as_it_has_factors_3_5_and_7(self def test_the_sound_for_3125_is_plang_as_it_has_a_factor_5(self): self.assertEqual(convert(3125), "Plang") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/rectangles/.meta/template.j2 b/exercises/practice/rectangles/.meta/template.j2 index 0d82cb8421..c9b1f623f7 100644 --- a/exercises/practice/rectangles/.meta/template.j2 +++ b/exercises/practice/rectangles/.meta/template.j2 @@ -8,5 +8,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] }}({{ input_strings }}), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} + \ No newline at end of file diff --git a/exercises/practice/rectangles/rectangles_test.py b/exercises/practice/rectangles/rectangles_test.py index 59c622de76..05938fc5b3 100644 --- a/exercises/practice/rectangles/rectangles_test.py +++ b/exercises/practice/rectangles/rectangles_test.py @@ -98,7 +98,3 @@ def test_rectangles_must_have_four_sides(self): ), 5, ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/resistor-color/.meta/template.j2 b/exercises/practice/resistor-color/.meta/template.j2 index e186e3562e..643a3b69f9 100644 --- a/exercises/practice/resistor-color/.meta/template.j2 +++ b/exercises/practice/resistor-color/.meta/template.j2 @@ -14,5 +14,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] | to_snake }}(), expected) {%- endif -%} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/resistor-color/resistor_color_test.py b/exercises/practice/resistor-color/resistor_color_test.py index 62ab48625f..631bc951e6 100644 --- a/exercises/practice/resistor-color/resistor_color_test.py +++ b/exercises/practice/resistor-color/resistor_color_test.py @@ -32,7 +32,3 @@ def test_colors(self): "white", ] self.assertEqual(colors(), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/rest-api/.meta/template.j2 b/exercises/practice/rest-api/.meta/template.j2 index 9bd4e751df..aa7afe8f4e 100644 --- a/exercises/practice/rest-api/.meta/template.j2 +++ b/exercises/practice/rest-api/.meta/template.j2 @@ -20,5 +20,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): expected = {{ case["expected"] }} self.assertDictEqual(json.loads(response), expected) {% endfor %}{% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/rest-api/rest_api_test.py b/exercises/practice/rest-api/rest_api_test.py index 8092aec237..4a5c525fa8 100644 --- a/exercises/practice/rest-api/rest_api_test.py +++ b/exercises/practice/rest-api/rest_api_test.py @@ -159,7 +159,3 @@ def test_lender_owes_borrower_same_as_new_loan(self): ] } self.assertDictEqual(json.loads(response), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/reverse-string/.meta/template.j2 b/exercises/practice/reverse-string/.meta/template.j2 index f7e1e41f3a..f76cc28e4d 100644 --- a/exercises/practice/reverse-string/.meta/template.j2 +++ b/exercises/practice/reverse-string/.meta/template.j2 @@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): "{{- case ["expected"] }}" ) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/reverse-string/reverse_string_test.py b/exercises/practice/reverse-string/reverse_string_test.py index 78212b41de..a1a28dc1e6 100644 --- a/exercises/practice/reverse-string/reverse_string_test.py +++ b/exercises/practice/reverse-string/reverse_string_test.py @@ -25,7 +25,3 @@ def test_a_palindrome(self): def test_an_even_sized_word(self): self.assertEqual(reverse("drawer"), "reward") - - -if __name__ == "__main__": - unittest.main() From 2a0a4f63425c543dcdbca8d64502bb6c5996e101 Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 12:35:55 -0700 Subject: [PATCH 492/826] Removed footer macros, regen tests, all still pass (#3426) [no important files changed] --- exercises/practice/sieve/.meta/template.j2 | 2 -- exercises/practice/sieve/sieve_test.py | 4 ---- exercises/practice/simple-cipher/.meta/template.j2 | 2 -- exercises/practice/simple-cipher/simple_cipher_test.py | 4 ---- exercises/practice/space-age/.meta/template.j2 | 3 +-- exercises/practice/space-age/space_age_test.py | 4 ---- exercises/practice/spiral-matrix/.meta/template.j2 | 2 -- exercises/practice/spiral-matrix/spiral_matrix_test.py | 4 ---- exercises/practice/sum-of-multiples/.meta/template.j2 | 2 -- exercises/practice/sum-of-multiples/sum_of_multiples_test.py | 4 ---- exercises/practice/triangle/.meta/template.j2 | 1 - exercises/practice/triangle/triangle_test.py | 4 ---- 12 files changed, 1 insertion(+), 35 deletions(-) diff --git a/exercises/practice/sieve/.meta/template.j2 b/exercises/practice/sieve/.meta/template.j2 index e26e4e4c1c..68a9db82a6 100644 --- a/exercises/practice/sieve/.meta/template.j2 +++ b/exercises/practice/sieve/.meta/template.j2 @@ -7,5 +7,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] }}({{ case["input"]["limit"] }}), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/sieve/sieve_test.py b/exercises/practice/sieve/sieve_test.py index d9fa67262b..91bb00e7ae 100644 --- a/exercises/practice/sieve/sieve_test.py +++ b/exercises/practice/sieve/sieve_test.py @@ -194,7 +194,3 @@ def test_find_primes_up_to_1000(self): 997, ], ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/simple-cipher/.meta/template.j2 b/exercises/practice/simple-cipher/.meta/template.j2 index ca20aa3011..ce9ad3e446 100644 --- a/exercises/practice/simple-cipher/.meta/template.j2 +++ b/exercises/practice/simple-cipher/.meta/template.j2 @@ -59,5 +59,3 @@ class {{ cases["description"] | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/simple-cipher/simple_cipher_test.py b/exercises/practice/simple-cipher/simple_cipher_test.py index 07c0c3d5b5..8b911d734b 100644 --- a/exercises/practice/simple-cipher/simple_cipher_test.py +++ b/exercises/practice/simple-cipher/simple_cipher_test.py @@ -64,7 +64,3 @@ def test_can_encode_messages_longer_than_the_key(self): def test_can_decode_messages_longer_than_the_key(self): cipher = Cipher("abc") self.assertEqual(cipher.decode("iboaqcnecbfcr"), "iamapandabear") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/space-age/.meta/template.j2 b/exercises/practice/space-age/.meta/template.j2 index a7822ad649..15c8c55032 100644 --- a/exercises/practice/space-age/.meta/template.j2 +++ b/exercises/practice/space-age/.meta/template.j2 @@ -9,5 +9,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual(SpaceAge({{ seconds }}).on_{{ planet | lower }}(), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} + \ No newline at end of file diff --git a/exercises/practice/space-age/space_age_test.py b/exercises/practice/space-age/space_age_test.py index b3f91ca067..1d248b19e3 100644 --- a/exercises/practice/space-age/space_age_test.py +++ b/exercises/practice/space-age/space_age_test.py @@ -31,7 +31,3 @@ def test_age_on_uranus(self): def test_age_on_neptune(self): self.assertEqual(SpaceAge(1821023456).on_neptune(), 0.35) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/spiral-matrix/.meta/template.j2 b/exercises/practice/spiral-matrix/.meta/template.j2 index b501e6d414..2a73e6bccb 100644 --- a/exercises/practice/spiral-matrix/.meta/template.j2 +++ b/exercises/practice/spiral-matrix/.meta/template.j2 @@ -7,5 +7,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] | to_snake }}({{ case["input"]["size"] }}), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/spiral-matrix/spiral_matrix_test.py b/exercises/practice/spiral-matrix/spiral_matrix_test.py index 1291342a89..3e6658cf54 100644 --- a/exercises/practice/spiral-matrix/spiral_matrix_test.py +++ b/exercises/practice/spiral-matrix/spiral_matrix_test.py @@ -37,7 +37,3 @@ def test_spiral_of_size_5(self): [13, 12, 11, 10, 9], ], ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/sum-of-multiples/.meta/template.j2 b/exercises/practice/sum-of-multiples/.meta/template.j2 index 6ffa327ac1..39dab6ada1 100644 --- a/exercises/practice/sum-of-multiples/.meta/template.j2 +++ b/exercises/practice/sum-of-multiples/.meta/template.j2 @@ -7,5 +7,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"].replace("sum", "sum_of_multiples") }}({{ case["input"]["limit"] }}, {{ case["input"]["factors"] }}), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py index f7e8bfb540..29d0ac7244 100644 --- a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py +++ b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py @@ -57,7 +57,3 @@ def test_solutions_using_include_exclude_must_extend_to_cardinality_greater_than self, ): self.assertEqual(sum_of_multiples(10000, [2, 3, 5, 7, 11]), 39614537) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/triangle/.meta/template.j2 b/exercises/practice/triangle/.meta/template.j2 index 8875bc5107..6d79c8c398 100644 --- a/exercises/practice/triangle/.meta/template.j2 +++ b/exercises/practice/triangle/.meta/template.j2 @@ -9,4 +9,3 @@ class {{ case["description"] | camel_case }}Test(unittest.TestCase): {% endfor %} {% endfor %} -{{ macros.footer() }} diff --git a/exercises/practice/triangle/triangle_test.py b/exercises/practice/triangle/triangle_test.py index 2de48d3372..f67d63fa69 100644 --- a/exercises/practice/triangle/triangle_test.py +++ b/exercises/practice/triangle/triangle_test.py @@ -76,7 +76,3 @@ def test_may_not_violate_triangle_inequality(self): def test_sides_may_be_floats(self): self.assertIs(scalene([0.5, 0.4, 0.6]), True) - - -if __name__ == "__main__": - unittest.main() From 61bda9ef83f8e12b4e1959d8ba773e37465f9e5a Mon Sep 17 00:00:00 2001 From: Angeleah Date: Fri, 14 Jul 2023 13:17:32 -0700 Subject: [PATCH 493/826] Remove footer macros, regen tests, all still pass (#3425) * Remove footer macros, regen tests, all still pass * Regenerated tests for Scale-generator [no important files changed] --------- Co-authored-by: BethanyG --- exercises/practice/rotational-cipher/.meta/template.j2 | 2 -- .../practice/rotational-cipher/rotational_cipher_test.py | 4 ---- exercises/practice/scale-generator/.meta/template.j2 | 3 --- exercises/practice/scale-generator/scale_generator_test.py | 4 ---- exercises/practice/scrabble-score/.meta/template.j2 | 4 +--- exercises/practice/scrabble-score/scrabble_score_test.py | 4 ---- exercises/practice/secret-handshake/.meta/template.j2 | 2 -- exercises/practice/secret-handshake/secret_handshake_test.py | 4 ---- 8 files changed, 1 insertion(+), 26 deletions(-) diff --git a/exercises/practice/rotational-cipher/.meta/template.j2 b/exercises/practice/rotational-cipher/.meta/template.j2 index 5a84641528..7db7871ea6 100644 --- a/exercises/practice/rotational-cipher/.meta/template.j2 +++ b/exercises/practice/rotational-cipher/.meta/template.j2 @@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): self.assertEqual({{ case["property"] }}("{{ text }}", {{ shiftKey }}), "{{ expected }}") {% endfor %} - -{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/rotational-cipher/rotational_cipher_test.py b/exercises/practice/rotational-cipher/rotational_cipher_test.py index 88b64d6edb..b6e760a4ed 100644 --- a/exercises/practice/rotational-cipher/rotational_cipher_test.py +++ b/exercises/practice/rotational-cipher/rotational_cipher_test.py @@ -40,7 +40,3 @@ def test_rotate_all_letters(self): rotate("The quick brown fox jumps over the lazy dog.", 13), "Gur dhvpx oebja sbk whzcf bire gur ynml qbt.", ) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/scale-generator/.meta/template.j2 b/exercises/practice/scale-generator/.meta/template.j2 index b65b5f0978..da566b8abe 100644 --- a/exercises/practice/scale-generator/.meta/template.j2 +++ b/exercises/practice/scale-generator/.meta/template.j2 @@ -25,6 +25,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases %} {{ test_supercase(supercase) }} {% endfor %} - - -{{ macros.footer() }} diff --git a/exercises/practice/scale-generator/scale_generator_test.py b/exercises/practice/scale-generator/scale_generator_test.py index 7edc118c77..5757fad316 100644 --- a/exercises/practice/scale-generator/scale_generator_test.py +++ b/exercises/practice/scale-generator/scale_generator_test.py @@ -78,7 +78,3 @@ def test_pentatonic(self): def test_enigmatic(self): expected = ["G", "G#", "B", "C#", "D#", "F", "F#", "G"] self.assertEqual(Scale("G").interval("mAMMMmm"), expected) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/scrabble-score/.meta/template.j2 b/exercises/practice/scrabble-score/.meta/template.j2 index f122b916b3..ea6c9ae224 100644 --- a/exercises/practice/scrabble-score/.meta/template.j2 +++ b/exercises/practice/scrabble-score/.meta/template.j2 @@ -13,6 +13,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} {{ test_case(case) }} {% endfor %} - - -{{ macros.footer() }} + \ No newline at end of file diff --git a/exercises/practice/scrabble-score/scrabble_score_test.py b/exercises/practice/scrabble-score/scrabble_score_test.py index e8d7eff75e..2ea8fd3240 100644 --- a/exercises/practice/scrabble-score/scrabble_score_test.py +++ b/exercises/practice/scrabble-score/scrabble_score_test.py @@ -40,7 +40,3 @@ def test_empty_input(self): def test_entire_alphabet_available(self): self.assertEqual(score("abcdefghijklmnopqrstuvwxyz"), 87) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/secret-handshake/.meta/template.j2 b/exercises/practice/secret-handshake/.meta/template.j2 index 459e9c9875..f5791a8bd8 100644 --- a/exercises/practice/secret-handshake/.meta/template.j2 +++ b/exercises/practice/secret-handshake/.meta/template.j2 @@ -6,5 +6,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): def test_{{ case["description"] | to_snake }}(self): self.assertEqual({{ case["property"] }}("{{ plugins.to_binary(case["input"]["number"]) }}"), {{ case["expected"] }}) {% endfor %} - -{{ macros.footer() }} diff --git a/exercises/practice/secret-handshake/secret_handshake_test.py b/exercises/practice/secret-handshake/secret_handshake_test.py index 959c866981..b9e534d5fa 100644 --- a/exercises/practice/secret-handshake/secret_handshake_test.py +++ b/exercises/practice/secret-handshake/secret_handshake_test.py @@ -44,7 +44,3 @@ def test_reverse_all_possible_actions(self): def test_do_nothing_for_zero(self): self.assertEqual(commands("00000"), []) - - -if __name__ == "__main__": - unittest.main() From c468f7c9fbac07d35cce16f328875c10de0ebb10 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Jul 2023 13:42:59 -0700 Subject: [PATCH 494/826] Final instruction updates of pig-latin, secret-handshake, and list-ops. (#3465) [no important files changed] --- exercises/practice/list-ops/.docs/instructions.md | 8 +++++--- exercises/practice/pig-latin/.docs/instructions.md | 3 ++- exercises/practice/secret-handshake/.meta/config.json | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md index d34533387a..ccfc2f8b2a 100644 --- a/exercises/practice/list-ops/.docs/instructions.md +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -12,6 +12,8 @@ The precise number and names of the operations to be implemented will be track d - `filter` (*given a predicate and a list, return the list of all items for which `predicate(item)` is True*); - `length` (*given a list, return the total number of items within it*); - `map` (*given a function and a list, return the list of the results of applying `function(item)` on all items*); -- `foldl` (*given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left using `function(accumulator, item)`*); -- `foldr` (*given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`*); -- `reverse` (*given a list, return a list with all the original items, but in reversed order*); +- `foldl` (*given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left*); +- `foldr` (*given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right*); +- `reverse` (*given a list, return a list with all the original items, but in reversed order*). + +Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant. diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index c9de5ca186..032905aa9b 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -8,11 +8,12 @@ It obeys a few simple rules (below), but when it's spoken quickly it's really di - **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). - **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. - Consonant sounds can be made up of multiple consonants, a.k.a. a consonant cluster (e.g. "chair" -> "airchay"). + Consonant sounds can be made up of multiple consonants, such as the "ch" in "chair" or "st" in "stand" (e.g. "chair" -> "airchay"). - **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). - **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). There are a few more rules for edge cases, and there are regional variants too. +Check the tests for all the details. Read more about [Pig Latin on Wikipedia][pig-latin]. diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json index cca237b478..7972aa1f5a 100644 --- a/exercises/practice/secret-handshake/.meta/config.json +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -31,5 +31,5 @@ }, "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", "source": "Bert, in Mary Poppins", - "source_url": "https://www.imdb.com/title/tt0058331/quotes/qt0437047" + "source_url": "https://www.imdb.com/title/tt0058331/quotes/?item=qt0437047" } From e7a6b0dc7dea6510a6fbaffb9aa035ef3bb1b543 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Jul 2023 15:52:15 -0700 Subject: [PATCH 495/826] [JinJa2] Corrected the macro used for comments on the test file. (#3373) * Corrected the macro for comments on the test file. * Added current_date (utcnow()) variable available for template macros. * Removed unnecessary datetime import from macros file. * Regenerated all practice exercise test files to add timestamp. * Changed `datetime.now(tz=timezone.utc)` to `datetime.now(tz=timezone.utc).date()` * Second regeneration to remove `timestamp` and just keep `date` for test files. [no important files changed] --- bin/generate_tests.py | 6 ++++-- config/generator_macros.j2 | 4 +++- exercises/practice/acronym/acronym_test.py | 4 +++- exercises/practice/affine-cipher/affine_cipher_test.py | 4 +++- exercises/practice/all-your-base/all_your_base_test.py | 4 +++- exercises/practice/allergies/allergies_test.py | 4 +++- exercises/practice/alphametics/alphametics_test.py | 4 +++- exercises/practice/anagram/anagram_test.py | 4 +++- .../practice/armstrong-numbers/armstrong_numbers_test.py | 4 +++- exercises/practice/atbash-cipher/atbash_cipher_test.py | 4 +++- exercises/practice/bank-account/bank_account_test.py | 4 +++- exercises/practice/beer-song/beer_song_test.py | 4 +++- .../practice/binary-search-tree/binary_search_tree_test.py | 4 +++- exercises/practice/binary-search/binary_search_test.py | 4 +++- exercises/practice/bob/bob_test.py | 4 +++- exercises/practice/book-store/book_store_test.py | 4 +++- exercises/practice/bottle-song/bottle_song_test.py | 4 +++- exercises/practice/bowling/bowling_test.py | 4 +++- exercises/practice/change/change_test.py | 4 +++- exercises/practice/circular-buffer/circular_buffer_test.py | 4 +++- exercises/practice/clock/clock_test.py | 4 +++- .../practice/collatz-conjecture/collatz_conjecture_test.py | 4 +++- exercises/practice/complex-numbers/complex_numbers_test.py | 4 +++- exercises/practice/connect/connect_test.py | 4 +++- exercises/practice/crypto-square/crypto_square_test.py | 4 +++- exercises/practice/custom-set/custom_set_test.py | 4 +++- exercises/practice/darts/darts_test.py | 4 +++- exercises/practice/diamond/diamond_test.py | 4 +++- .../difference-of-squares/difference_of_squares_test.py | 4 +++- exercises/practice/diffie-hellman/diffie_hellman_test.py | 4 +++- exercises/practice/dnd-character/dnd_character_test.py | 4 +++- exercises/practice/dominoes/dominoes_test.py | 4 +++- exercises/practice/etl/etl_test.py | 4 +++- exercises/practice/flatten-array/flatten_array_test.py | 4 +++- exercises/practice/food-chain/food_chain_test.py | 4 +++- exercises/practice/forth/forth_test.py | 4 +++- exercises/practice/gigasecond/gigasecond_test.py | 4 +++- exercises/practice/go-counting/go_counting_test.py | 4 +++- exercises/practice/grade-school/grade_school_test.py | 4 +++- exercises/practice/grains/grains_test.py | 4 +++- exercises/practice/grep/grep_test.py | 4 +++- exercises/practice/hamming/hamming_test.py | 4 +++- exercises/practice/high-scores/high_scores_test.py | 4 +++- exercises/practice/house/house_test.py | 4 +++- exercises/practice/isbn-verifier/isbn_verifier_test.py | 4 +++- exercises/practice/isogram/isogram_test.py | 4 +++- .../killer-sudoku-helper/killer_sudoku_helper_test.py | 4 +++- .../kindergarten-garden/kindergarten_garden_test.py | 4 +++- exercises/practice/knapsack/knapsack_test.py | 4 +++- .../largest-series-product/largest_series_product_test.py | 4 +++- exercises/practice/leap/leap_test.py | 4 +++- exercises/practice/ledger/ledger_test.py | 4 +++- exercises/practice/linked-list/linked_list_test.py | 4 +++- exercises/practice/list-ops/list_ops_test.py | 4 +++- exercises/practice/luhn/luhn_test.py | 4 +++- exercises/practice/markdown/markdown_test.py | 4 +++- .../practice/matching-brackets/matching_brackets_test.py | 4 +++- exercises/practice/matrix/matrix_test.py | 4 +++- exercises/practice/meetup/meetup_test.py | 4 +++- exercises/practice/minesweeper/minesweeper_test.py | 4 +++- exercises/practice/nth-prime/nth_prime_test.py | 4 +++- exercises/practice/ocr-numbers/ocr_numbers_test.py | 4 +++- .../palindrome-products/palindrome_products_test.py | 4 +++- exercises/practice/pangram/pangram_test.py | 4 +++- .../practice/pascals-triangle/pascals_triangle_test.py | 4 +++- exercises/practice/perfect-numbers/perfect_numbers_test.py | 4 +++- exercises/practice/phone-number/phone_number_test.py | 4 +++- exercises/practice/pig-latin/pig_latin_test.py | 4 +++- exercises/practice/poker/poker_test.py | 4 +++- exercises/practice/pov/pov_test.py | 4 +++- exercises/practice/prime-factors/prime_factors_test.py | 4 +++- .../protein-translation/protein_translation_test.py | 4 +++- exercises/practice/proverb/proverb_test.py | 4 +++- .../pythagorean-triplet/pythagorean_triplet_test.py | 4 +++- exercises/practice/queen-attack/queen_attack_test.py | 4 +++- .../practice/rail-fence-cipher/rail_fence_cipher_test.py | 4 +++- exercises/practice/raindrops/raindrops_test.py | 4 +++- .../practice/rational-numbers/rational_numbers_test.py | 4 +++- exercises/practice/react/react_test.py | 4 +++- exercises/practice/rectangles/rectangles_test.py | 4 +++- .../practice/resistor-color-duo/resistor_color_duo_test.py | 4 +++- .../resistor-color-trio/resistor_color_trio_test.py | 4 +++- exercises/practice/resistor-color/resistor_color_test.py | 4 +++- exercises/practice/rest-api/rest_api_test.py | 4 +++- exercises/practice/reverse-string/reverse_string_test.py | 4 +++- .../practice/rna-transcription/rna_transcription_test.py | 4 +++- exercises/practice/robot-simulator/robot_simulator_test.py | 4 +++- exercises/practice/roman-numerals/roman_numerals_test.py | 4 +++- .../practice/rotational-cipher/rotational_cipher_test.py | 4 +++- .../run-length-encoding/run_length_encoding_test.py | 4 +++- exercises/practice/saddle-points/saddle_points_test.py | 4 +++- exercises/practice/satellite/satellite_test.py | 4 +++- exercises/practice/say/say_test.py | 4 +++- exercises/practice/scale-generator/scale_generator_test.py | 4 +++- exercises/practice/scrabble-score/scrabble_score_test.py | 4 +++- .../practice/secret-handshake/secret_handshake_test.py | 4 +++- exercises/practice/series/series_test.py | 4 +++- exercises/practice/sgf-parsing/sgf_parsing_test.py | 4 +++- exercises/practice/sieve/sieve_test.py | 4 +++- exercises/practice/simple-cipher/simple_cipher_test.py | 4 +++- exercises/practice/space-age/space_age_test.py | 4 +++- exercises/practice/spiral-matrix/spiral_matrix_test.py | 4 +++- exercises/practice/square-root/square_root_test.py | 4 +++- exercises/practice/sublist/sublist_test.py | 4 +++- .../practice/sum-of-multiples/sum_of_multiples_test.py | 4 +++- exercises/practice/tournament/tournament_test.py | 4 +++- exercises/practice/transpose/transpose_test.py | 4 +++- exercises/practice/triangle/triangle_test.py | 4 +++- exercises/practice/twelve-days/twelve_days_test.py | 4 +++- exercises/practice/two-bucket/two_bucket_test.py | 4 +++- exercises/practice/two-fer/two_fer_test.py | 4 +++- .../variable_length_quantity_test.py | 4 +++- exercises/practice/word-count/word_count_test.py | 4 +++- exercises/practice/word-search/word_search_test.py | 4 +++- exercises/practice/wordy/wordy_test.py | 4 +++- exercises/practice/yacht/yacht_test.py | 4 +++- exercises/practice/zebra-puzzle/zebra_puzzle_test.py | 4 +++- exercises/practice/zipper/zipper_test.py | 4 +++- 118 files changed, 355 insertions(+), 119 deletions(-) diff --git a/bin/generate_tests.py b/bin/generate_tests.py index 8dc74936b1..d5fc4c5c5e 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -17,12 +17,13 @@ from githelp import Repo _py = sys.version_info -if _py.major < 3 or (_py.major == 3 and _py.minor < 6): - print("Python version must be at least 3.6") +if _py.major < 3 or (_py.major == 3 and _py.minor < 7): + print("Python version must be at least 3.7") sys.exit(1) import argparse from datetime import datetime +from datetime import timezone import difflib import filecmp import importlib.util @@ -393,6 +394,7 @@ def generate( env.filters["zip"] = zip env.filters["parse_datetime"] = parse_datetime env.filters["escape_invalid_escapes"] = escape_invalid_escapes + env.globals["current_date"] = datetime.now(tz=timezone.utc).date() env.tests["error_case"] = error_case result = True for exercise in sorted(Path("exercises/practice").glob(exercise_glob)): diff --git a/config/generator_macros.j2 b/config/generator_macros.j2 index 76a8c07230..32af5d5c9f 100644 --- a/config/generator_macros.j2 +++ b/config/generator_macros.j2 @@ -10,7 +10,9 @@ {% endmacro -%} {% macro canonical_ref() -%} -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/{{ exercise }}/canonical-data.json +# File last updated on {{ current_date }} {%- endmacro %} {% macro header(imports=[], ignore=[]) -%} diff --git a/exercises/practice/acronym/acronym_test.py b/exercises/practice/acronym/acronym_test.py index 19e4e48662..444f60cb55 100644 --- a/exercises/practice/acronym/acronym_test.py +++ b/exercises/practice/acronym/acronym_test.py @@ -4,7 +4,9 @@ abbreviate, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json +# File last updated on 2023-07-14 class AcronymTest(unittest.TestCase): diff --git a/exercises/practice/affine-cipher/affine_cipher_test.py b/exercises/practice/affine-cipher/affine_cipher_test.py index fb9cd4a108..713e391023 100644 --- a/exercises/practice/affine-cipher/affine_cipher_test.py +++ b/exercises/practice/affine-cipher/affine_cipher_test.py @@ -5,7 +5,9 @@ encode, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json +# File last updated on 2023-07-14 class AffineCipherTest(unittest.TestCase): diff --git a/exercises/practice/all-your-base/all_your_base_test.py b/exercises/practice/all-your-base/all_your_base_test.py index 0191754acb..182237e4e1 100644 --- a/exercises/practice/all-your-base/all_your_base_test.py +++ b/exercises/practice/all-your-base/all_your_base_test.py @@ -4,7 +4,9 @@ rebase, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json +# File last updated on 2023-07-14 class AllYourBaseTest(unittest.TestCase): diff --git a/exercises/practice/allergies/allergies_test.py b/exercises/practice/allergies/allergies_test.py index 4525db8edb..fa4e1c55ee 100644 --- a/exercises/practice/allergies/allergies_test.py +++ b/exercises/practice/allergies/allergies_test.py @@ -4,7 +4,9 @@ Allergies, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json +# File last updated on 2023-07-14 class AllergiesTest(unittest.TestCase): diff --git a/exercises/practice/alphametics/alphametics_test.py b/exercises/practice/alphametics/alphametics_test.py index 8853e1e71c..5557b3c8eb 100644 --- a/exercises/practice/alphametics/alphametics_test.py +++ b/exercises/practice/alphametics/alphametics_test.py @@ -4,7 +4,9 @@ solve, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json +# File last updated on 2023-07-14 class AlphameticsTest(unittest.TestCase): diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py index f06ad9fb53..85087e8580 100644 --- a/exercises/practice/anagram/anagram_test.py +++ b/exercises/practice/anagram/anagram_test.py @@ -4,7 +4,9 @@ find_anagrams, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json +# File last updated on 2023-07-14 class AnagramTest(unittest.TestCase): diff --git a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py index 6a4bd96e83..ed6907d884 100644 --- a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py +++ b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py @@ -4,7 +4,9 @@ is_armstrong_number, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json +# File last updated on 2023-07-14 class ArmstrongNumbersTest(unittest.TestCase): diff --git a/exercises/practice/atbash-cipher/atbash_cipher_test.py b/exercises/practice/atbash-cipher/atbash_cipher_test.py index fcc1a25e68..4e630e3a12 100644 --- a/exercises/practice/atbash-cipher/atbash_cipher_test.py +++ b/exercises/practice/atbash-cipher/atbash_cipher_test.py @@ -5,7 +5,9 @@ encode, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/atbash-cipher/canonical-data.json +# File last updated on 2023-07-14 class AtbashCipherTest(unittest.TestCase): diff --git a/exercises/practice/bank-account/bank_account_test.py b/exercises/practice/bank-account/bank_account_test.py index 76ddbfe404..9e974da8fa 100644 --- a/exercises/practice/bank-account/bank_account_test.py +++ b/exercises/practice/bank-account/bank_account_test.py @@ -4,7 +4,9 @@ BankAccount, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/bank-account/canonical-data.json +# File last updated on 2023-07-14 class BankAccountTest(unittest.TestCase): diff --git a/exercises/practice/beer-song/beer_song_test.py b/exercises/practice/beer-song/beer_song_test.py index 2aca5bae85..b11f11d1f5 100644 --- a/exercises/practice/beer-song/beer_song_test.py +++ b/exercises/practice/beer-song/beer_song_test.py @@ -4,7 +4,9 @@ recite, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/beer-song/canonical-data.json +# File last updated on 2023-07-14 class BeerSongTest(unittest.TestCase): diff --git a/exercises/practice/binary-search-tree/binary_search_tree_test.py b/exercises/practice/binary-search-tree/binary_search_tree_test.py index 9a4a71933d..479cd3411f 100644 --- a/exercises/practice/binary-search-tree/binary_search_tree_test.py +++ b/exercises/practice/binary-search-tree/binary_search_tree_test.py @@ -5,7 +5,9 @@ TreeNode, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search-tree/canonical-data.json +# File last updated on 2023-07-14 class BinarySearchTreeTest(unittest.TestCase): diff --git a/exercises/practice/binary-search/binary_search_test.py b/exercises/practice/binary-search/binary_search_test.py index 9f883d1767..325622ad37 100644 --- a/exercises/practice/binary-search/binary_search_test.py +++ b/exercises/practice/binary-search/binary_search_test.py @@ -4,7 +4,9 @@ find, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search/canonical-data.json +# File last updated on 2023-07-14 class BinarySearchTest(unittest.TestCase): diff --git a/exercises/practice/bob/bob_test.py b/exercises/practice/bob/bob_test.py index c033d34d8b..67c7f64f7d 100644 --- a/exercises/practice/bob/bob_test.py +++ b/exercises/practice/bob/bob_test.py @@ -4,7 +4,9 @@ response, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json +# File last updated on 2023-07-14 class BobTest(unittest.TestCase): diff --git a/exercises/practice/book-store/book_store_test.py b/exercises/practice/book-store/book_store_test.py index 06feb75acb..329e5410f2 100644 --- a/exercises/practice/book-store/book_store_test.py +++ b/exercises/practice/book-store/book_store_test.py @@ -4,7 +4,9 @@ total, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/book-store/canonical-data.json +# File last updated on 2023-07-14 class BookStoreTest(unittest.TestCase): diff --git a/exercises/practice/bottle-song/bottle_song_test.py b/exercises/practice/bottle-song/bottle_song_test.py index 3c3108f929..f8c33aac2b 100644 --- a/exercises/practice/bottle-song/bottle_song_test.py +++ b/exercises/practice/bottle-song/bottle_song_test.py @@ -4,7 +4,9 @@ recite, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/bottle-song/canonical-data.json +# File last updated on 2023-07-14 class BottleSongTest(unittest.TestCase): diff --git a/exercises/practice/bowling/bowling_test.py b/exercises/practice/bowling/bowling_test.py index 89d0a45f0b..8e04a4c814 100644 --- a/exercises/practice/bowling/bowling_test.py +++ b/exercises/practice/bowling/bowling_test.py @@ -4,7 +4,9 @@ BowlingGame, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json +# File last updated on 2023-07-14 class BowlingTest(unittest.TestCase): diff --git a/exercises/practice/change/change_test.py b/exercises/practice/change/change_test.py index 6b335174ef..74fead1196 100644 --- a/exercises/practice/change/change_test.py +++ b/exercises/practice/change/change_test.py @@ -4,7 +4,9 @@ find_fewest_coins, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json +# File last updated on 2023-07-14 class ChangeTest(unittest.TestCase): diff --git a/exercises/practice/circular-buffer/circular_buffer_test.py b/exercises/practice/circular-buffer/circular_buffer_test.py index 403ee1b5ff..411df16bbd 100644 --- a/exercises/practice/circular-buffer/circular_buffer_test.py +++ b/exercises/practice/circular-buffer/circular_buffer_test.py @@ -6,7 +6,9 @@ BufferFullException, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/circular-buffer/canonical-data.json +# File last updated on 2023-07-14 class CircularBufferTest(unittest.TestCase): diff --git a/exercises/practice/clock/clock_test.py b/exercises/practice/clock/clock_test.py index b2c6fe9d89..4fb17b4b1e 100644 --- a/exercises/practice/clock/clock_test.py +++ b/exercises/practice/clock/clock_test.py @@ -4,7 +4,9 @@ Clock, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/clock/canonical-data.json +# File last updated on 2023-07-14 class ClockTest(unittest.TestCase): diff --git a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py index b9c6df93c8..36c386ee39 100644 --- a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py +++ b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py @@ -4,7 +4,9 @@ steps, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json +# File last updated on 2023-07-14 class CollatzConjectureTest(unittest.TestCase): diff --git a/exercises/practice/complex-numbers/complex_numbers_test.py b/exercises/practice/complex-numbers/complex_numbers_test.py index 2eca784969..995439763a 100644 --- a/exercises/practice/complex-numbers/complex_numbers_test.py +++ b/exercises/practice/complex-numbers/complex_numbers_test.py @@ -6,7 +6,9 @@ ComplexNumber, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json +# File last updated on 2023-07-14 class ComplexNumbersTest(unittest.TestCase): diff --git a/exercises/practice/connect/connect_test.py b/exercises/practice/connect/connect_test.py index 17c786c865..a5b6f32444 100644 --- a/exercises/practice/connect/connect_test.py +++ b/exercises/practice/connect/connect_test.py @@ -4,7 +4,9 @@ ConnectGame, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json +# File last updated on 2023-07-14 class ConnectTest(unittest.TestCase): diff --git a/exercises/practice/crypto-square/crypto_square_test.py b/exercises/practice/crypto-square/crypto_square_test.py index 553728f3fe..c2845ef562 100644 --- a/exercises/practice/crypto-square/crypto_square_test.py +++ b/exercises/practice/crypto-square/crypto_square_test.py @@ -4,7 +4,9 @@ cipher_text, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json +# File last updated on 2023-07-14 class CryptoSquareTest(unittest.TestCase): diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index 30c945ac73..ecad09fdba 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -4,7 +4,9 @@ CustomSet, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json +# File last updated on 2023-07-14 class CustomSetTest(unittest.TestCase): diff --git a/exercises/practice/darts/darts_test.py b/exercises/practice/darts/darts_test.py index 94a9c5ed3c..9f9a6a323e 100644 --- a/exercises/practice/darts/darts_test.py +++ b/exercises/practice/darts/darts_test.py @@ -4,7 +4,9 @@ score, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/darts/canonical-data.json +# File last updated on 2023-07-14 class DartsTest(unittest.TestCase): diff --git a/exercises/practice/diamond/diamond_test.py b/exercises/practice/diamond/diamond_test.py index 9ed44ee198..335b0bd799 100644 --- a/exercises/practice/diamond/diamond_test.py +++ b/exercises/practice/diamond/diamond_test.py @@ -4,7 +4,9 @@ rows, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/diamond/canonical-data.json +# File last updated on 2023-07-14 class DiamondTest(unittest.TestCase): diff --git a/exercises/practice/difference-of-squares/difference_of_squares_test.py b/exercises/practice/difference-of-squares/difference_of_squares_test.py index fa39f03e55..d38681f742 100644 --- a/exercises/practice/difference-of-squares/difference_of_squares_test.py +++ b/exercises/practice/difference-of-squares/difference_of_squares_test.py @@ -6,7 +6,9 @@ sum_of_squares, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/difference-of-squares/canonical-data.json +# File last updated on 2023-07-14 class DifferenceOfSquaresTest(unittest.TestCase): diff --git a/exercises/practice/diffie-hellman/diffie_hellman_test.py b/exercises/practice/diffie-hellman/diffie_hellman_test.py index 716b7ba191..60c514d9f8 100644 --- a/exercises/practice/diffie-hellman/diffie_hellman_test.py +++ b/exercises/practice/diffie-hellman/diffie_hellman_test.py @@ -6,7 +6,9 @@ secret, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/diffie-hellman/canonical-data.json +# File last updated on 2023-07-14 class DiffieHellmanTest(unittest.TestCase): diff --git a/exercises/practice/dnd-character/dnd_character_test.py b/exercises/practice/dnd-character/dnd_character_test.py index 3f370bc4bd..d97cbfda62 100644 --- a/exercises/practice/dnd-character/dnd_character_test.py +++ b/exercises/practice/dnd-character/dnd_character_test.py @@ -5,7 +5,9 @@ modifier, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/dnd-character/canonical-data.json +# File last updated on 2023-07-14 class DndCharacterTest(unittest.TestCase): diff --git a/exercises/practice/dominoes/dominoes_test.py b/exercises/practice/dominoes/dominoes_test.py index a9b8c90dd8..a9d82512c2 100644 --- a/exercises/practice/dominoes/dominoes_test.py +++ b/exercises/practice/dominoes/dominoes_test.py @@ -4,7 +4,9 @@ can_chain, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json +# File last updated on 2023-07-14 class DominoesTest(unittest.TestCase): diff --git a/exercises/practice/etl/etl_test.py b/exercises/practice/etl/etl_test.py index fbd57fd289..b2ce956fe4 100644 --- a/exercises/practice/etl/etl_test.py +++ b/exercises/practice/etl/etl_test.py @@ -4,7 +4,9 @@ transform, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/etl/canonical-data.json +# File last updated on 2023-07-14 class EtlTest(unittest.TestCase): diff --git a/exercises/practice/flatten-array/flatten_array_test.py b/exercises/practice/flatten-array/flatten_array_test.py index 3038d1a00f..8271ce1d06 100644 --- a/exercises/practice/flatten-array/flatten_array_test.py +++ b/exercises/practice/flatten-array/flatten_array_test.py @@ -4,7 +4,9 @@ flatten, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/flatten-array/canonical-data.json +# File last updated on 2023-07-14 class FlattenArrayTest(unittest.TestCase): diff --git a/exercises/practice/food-chain/food_chain_test.py b/exercises/practice/food-chain/food_chain_test.py index f87f4cc9a2..195c1ab117 100644 --- a/exercises/practice/food-chain/food_chain_test.py +++ b/exercises/practice/food-chain/food_chain_test.py @@ -4,7 +4,9 @@ recite, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/food-chain/canonical-data.json +# File last updated on 2023-07-14 class FoodChainTest(unittest.TestCase): diff --git a/exercises/practice/forth/forth_test.py b/exercises/practice/forth/forth_test.py index f565c6455e..07778ceb3a 100644 --- a/exercises/practice/forth/forth_test.py +++ b/exercises/practice/forth/forth_test.py @@ -5,7 +5,9 @@ StackUnderflowError, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json +# File last updated on 2023-07-14 class ForthTest(unittest.TestCase): diff --git a/exercises/practice/gigasecond/gigasecond_test.py b/exercises/practice/gigasecond/gigasecond_test.py index e7b5f0837f..c1e24d4bc4 100644 --- a/exercises/practice/gigasecond/gigasecond_test.py +++ b/exercises/practice/gigasecond/gigasecond_test.py @@ -5,7 +5,9 @@ add, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/gigasecond/canonical-data.json +# File last updated on 2023-07-14 class GigasecondTest(unittest.TestCase): diff --git a/exercises/practice/go-counting/go_counting_test.py b/exercises/practice/go-counting/go_counting_test.py index 30972ca6a2..e0920459cb 100644 --- a/exercises/practice/go-counting/go_counting_test.py +++ b/exercises/practice/go-counting/go_counting_test.py @@ -7,7 +7,9 @@ NONE, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/go-counting/canonical-data.json +# File last updated on 2023-07-14 class GoCountingTest(unittest.TestCase): diff --git a/exercises/practice/grade-school/grade_school_test.py b/exercises/practice/grade-school/grade_school_test.py index 4672efe88e..19509de653 100644 --- a/exercises/practice/grade-school/grade_school_test.py +++ b/exercises/practice/grade-school/grade_school_test.py @@ -4,7 +4,9 @@ School, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/grade-school/canonical-data.json +# File last updated on 2023-07-14 class GradeSchoolTest(unittest.TestCase): diff --git a/exercises/practice/grains/grains_test.py b/exercises/practice/grains/grains_test.py index f7e277c070..5a4de3a877 100644 --- a/exercises/practice/grains/grains_test.py +++ b/exercises/practice/grains/grains_test.py @@ -5,7 +5,9 @@ total, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json +# File last updated on 2023-07-14 class GrainsTest(unittest.TestCase): diff --git a/exercises/practice/grep/grep_test.py b/exercises/practice/grep/grep_test.py index 87c76ac829..ff81e36d8d 100644 --- a/exercises/practice/grep/grep_test.py +++ b/exercises/practice/grep/grep_test.py @@ -4,7 +4,9 @@ grep, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/grep/canonical-data.json +# File last updated on 2023-07-14 import io from unittest import mock diff --git a/exercises/practice/hamming/hamming_test.py b/exercises/practice/hamming/hamming_test.py index 06a08b09dd..e9fcadea9e 100644 --- a/exercises/practice/hamming/hamming_test.py +++ b/exercises/practice/hamming/hamming_test.py @@ -4,7 +4,9 @@ distance, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/hamming/canonical-data.json +# File last updated on 2023-07-14 class HammingTest(unittest.TestCase): diff --git a/exercises/practice/high-scores/high_scores_test.py b/exercises/practice/high-scores/high_scores_test.py index f1406dd852..ec716a489c 100644 --- a/exercises/practice/high-scores/high_scores_test.py +++ b/exercises/practice/high-scores/high_scores_test.py @@ -4,7 +4,9 @@ HighScores, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/high-scores/canonical-data.json +# File last updated on 2023-07-14 class HighScoresTest(unittest.TestCase): diff --git a/exercises/practice/house/house_test.py b/exercises/practice/house/house_test.py index 8247675823..6ad16a0ccb 100644 --- a/exercises/practice/house/house_test.py +++ b/exercises/practice/house/house_test.py @@ -4,7 +4,9 @@ recite, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/house/canonical-data.json +# File last updated on 2023-07-14 class HouseTest(unittest.TestCase): diff --git a/exercises/practice/isbn-verifier/isbn_verifier_test.py b/exercises/practice/isbn-verifier/isbn_verifier_test.py index 22fd28922f..aa3d954d22 100644 --- a/exercises/practice/isbn-verifier/isbn_verifier_test.py +++ b/exercises/practice/isbn-verifier/isbn_verifier_test.py @@ -4,7 +4,9 @@ is_valid, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/isbn-verifier/canonical-data.json +# File last updated on 2023-07-14 class IsbnVerifierTest(unittest.TestCase): diff --git a/exercises/practice/isogram/isogram_test.py b/exercises/practice/isogram/isogram_test.py index 8d00d2b5f5..99940e3ddf 100644 --- a/exercises/practice/isogram/isogram_test.py +++ b/exercises/practice/isogram/isogram_test.py @@ -4,7 +4,9 @@ is_isogram, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/isogram/canonical-data.json +# File last updated on 2023-07-14 class IsogramTest(unittest.TestCase): diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py index b573205465..3a7f4c0dcc 100644 --- a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py @@ -4,7 +4,9 @@ combinations, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/killer-sudoku-helper/canonical-data.json +# File last updated on 2023-07-14 class KillerSudokuHelperTest(unittest.TestCase): diff --git a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py index 95095cabed..5773dfa390 100644 --- a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py +++ b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py @@ -4,7 +4,9 @@ Garden, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/kindergarten-garden/canonical-data.json +# File last updated on 2023-07-14 class KindergartenGardenTest(unittest.TestCase): diff --git a/exercises/practice/knapsack/knapsack_test.py b/exercises/practice/knapsack/knapsack_test.py index 937df1bb0f..20429bb263 100644 --- a/exercises/practice/knapsack/knapsack_test.py +++ b/exercises/practice/knapsack/knapsack_test.py @@ -4,7 +4,9 @@ maximum_value, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json +# File last updated on 2023-07-14 class KnapsackTest(unittest.TestCase): diff --git a/exercises/practice/largest-series-product/largest_series_product_test.py b/exercises/practice/largest-series-product/largest_series_product_test.py index ba8ae10dc8..8b02dfbbb7 100644 --- a/exercises/practice/largest-series-product/largest_series_product_test.py +++ b/exercises/practice/largest-series-product/largest_series_product_test.py @@ -4,7 +4,9 @@ largest_product, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/largest-series-product/canonical-data.json +# File last updated on 2023-07-14 class LargestSeriesProductTest(unittest.TestCase): diff --git a/exercises/practice/leap/leap_test.py b/exercises/practice/leap/leap_test.py index 6ba0f949b1..2b25b36a6a 100644 --- a/exercises/practice/leap/leap_test.py +++ b/exercises/practice/leap/leap_test.py @@ -4,7 +4,9 @@ leap_year, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json +# File last updated on 2023-07-14 class LeapTest(unittest.TestCase): diff --git a/exercises/practice/ledger/ledger_test.py b/exercises/practice/ledger/ledger_test.py index c1615666ce..2c51a4d771 100644 --- a/exercises/practice/ledger/ledger_test.py +++ b/exercises/practice/ledger/ledger_test.py @@ -5,7 +5,9 @@ create_entry, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/ledger/canonical-data.json +# File last updated on 2023-07-14 class LedgerTest(unittest.TestCase): diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 3af5bc221e..c0b44de4d9 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -4,7 +4,9 @@ LinkedList, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/linked-list/canonical-data.json +# File last updated on 2023-07-14 class LinkedListTest(unittest.TestCase): diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index cb015acc66..7bd60f4f21 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -11,7 +11,9 @@ map as list_ops_map, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/list-ops/canonical-data.json +# File last updated on 2023-07-14 class ListOpsTest(unittest.TestCase): diff --git a/exercises/practice/luhn/luhn_test.py b/exercises/practice/luhn/luhn_test.py index bbc1f8eb1f..dd309dd72f 100644 --- a/exercises/practice/luhn/luhn_test.py +++ b/exercises/practice/luhn/luhn_test.py @@ -4,7 +4,9 @@ Luhn, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/luhn/canonical-data.json +# File last updated on 2023-07-14 class LuhnTest(unittest.TestCase): diff --git a/exercises/practice/markdown/markdown_test.py b/exercises/practice/markdown/markdown_test.py index 5c059c725a..b79353cdd7 100644 --- a/exercises/practice/markdown/markdown_test.py +++ b/exercises/practice/markdown/markdown_test.py @@ -4,7 +4,9 @@ parse, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/markdown/canonical-data.json +# File last updated on 2023-07-14 class MarkdownTest(unittest.TestCase): diff --git a/exercises/practice/matching-brackets/matching_brackets_test.py b/exercises/practice/matching-brackets/matching_brackets_test.py index fb270ae8bc..b83c341a29 100644 --- a/exercises/practice/matching-brackets/matching_brackets_test.py +++ b/exercises/practice/matching-brackets/matching_brackets_test.py @@ -4,7 +4,9 @@ is_paired, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/matching-brackets/canonical-data.json +# File last updated on 2023-07-14 class MatchingBracketsTest(unittest.TestCase): diff --git a/exercises/practice/matrix/matrix_test.py b/exercises/practice/matrix/matrix_test.py index 10820ab6c1..8573ccf9a6 100644 --- a/exercises/practice/matrix/matrix_test.py +++ b/exercises/practice/matrix/matrix_test.py @@ -4,7 +4,9 @@ Matrix, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/matrix/canonical-data.json +# File last updated on 2023-07-14 class MatrixTest(unittest.TestCase): diff --git a/exercises/practice/meetup/meetup_test.py b/exercises/practice/meetup/meetup_test.py index 9ecc3d8a1d..a9b3721c03 100644 --- a/exercises/practice/meetup/meetup_test.py +++ b/exercises/practice/meetup/meetup_test.py @@ -6,7 +6,9 @@ MeetupDayException, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/meetup/canonical-data.json +# File last updated on 2023-07-14 class MeetupTest(unittest.TestCase): diff --git a/exercises/practice/minesweeper/minesweeper_test.py b/exercises/practice/minesweeper/minesweeper_test.py index d5e6b78b7f..32927a95ef 100644 --- a/exercises/practice/minesweeper/minesweeper_test.py +++ b/exercises/practice/minesweeper/minesweeper_test.py @@ -4,7 +4,9 @@ annotate, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/minesweeper/canonical-data.json +# File last updated on 2023-07-14 class MinesweeperTest(unittest.TestCase): diff --git a/exercises/practice/nth-prime/nth_prime_test.py b/exercises/practice/nth-prime/nth_prime_test.py index 2a3e1b3f2e..50a68a05e2 100644 --- a/exercises/practice/nth-prime/nth_prime_test.py +++ b/exercises/practice/nth-prime/nth_prime_test.py @@ -4,7 +4,9 @@ prime, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/nth-prime/canonical-data.json +# File last updated on 2023-07-14 def prime_range(n): diff --git a/exercises/practice/ocr-numbers/ocr_numbers_test.py b/exercises/practice/ocr-numbers/ocr_numbers_test.py index eeafb2fb31..d3a11134dd 100644 --- a/exercises/practice/ocr-numbers/ocr_numbers_test.py +++ b/exercises/practice/ocr-numbers/ocr_numbers_test.py @@ -4,7 +4,9 @@ convert, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/ocr-numbers/canonical-data.json +# File last updated on 2023-07-14 class OcrNumbersTest(unittest.TestCase): diff --git a/exercises/practice/palindrome-products/palindrome_products_test.py b/exercises/practice/palindrome-products/palindrome_products_test.py index 0fd7eaece3..e748b8703c 100644 --- a/exercises/practice/palindrome-products/palindrome_products_test.py +++ b/exercises/practice/palindrome-products/palindrome_products_test.py @@ -5,7 +5,9 @@ smallest, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/palindrome-products/canonical-data.json +# File last updated on 2023-07-14 class PalindromeProductsTest(unittest.TestCase): diff --git a/exercises/practice/pangram/pangram_test.py b/exercises/practice/pangram/pangram_test.py index 3f8c9aa7f3..2e5179acee 100644 --- a/exercises/practice/pangram/pangram_test.py +++ b/exercises/practice/pangram/pangram_test.py @@ -4,7 +4,9 @@ is_pangram, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pangram/canonical-data.json +# File last updated on 2023-07-14 class PangramTest(unittest.TestCase): diff --git a/exercises/practice/pascals-triangle/pascals_triangle_test.py b/exercises/practice/pascals-triangle/pascals_triangle_test.py index dc7b7ce111..a0b253af3e 100644 --- a/exercises/practice/pascals-triangle/pascals_triangle_test.py +++ b/exercises/practice/pascals-triangle/pascals_triangle_test.py @@ -5,7 +5,9 @@ rows, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pascals-triangle/canonical-data.json +# File last updated on 2023-07-14 TRIANGLE = [ [1], diff --git a/exercises/practice/perfect-numbers/perfect_numbers_test.py b/exercises/practice/perfect-numbers/perfect_numbers_test.py index 786f92630e..512600f12d 100644 --- a/exercises/practice/perfect-numbers/perfect_numbers_test.py +++ b/exercises/practice/perfect-numbers/perfect_numbers_test.py @@ -4,7 +4,9 @@ classify, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/perfect-numbers/canonical-data.json +# File last updated on 2023-07-14 class PerfectNumbersTest(unittest.TestCase): diff --git a/exercises/practice/phone-number/phone_number_test.py b/exercises/practice/phone-number/phone_number_test.py index 72ff3b099a..1002c6b7a1 100644 --- a/exercises/practice/phone-number/phone_number_test.py +++ b/exercises/practice/phone-number/phone_number_test.py @@ -4,7 +4,9 @@ PhoneNumber, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/phone-number/canonical-data.json +# File last updated on 2023-07-14 class PhoneNumberTest(unittest.TestCase): diff --git a/exercises/practice/pig-latin/pig_latin_test.py b/exercises/practice/pig-latin/pig_latin_test.py index cf46ae3c1f..997f0284d5 100644 --- a/exercises/practice/pig-latin/pig_latin_test.py +++ b/exercises/practice/pig-latin/pig_latin_test.py @@ -4,7 +4,9 @@ translate, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json +# File last updated on 2023-07-14 class PigLatinTest(unittest.TestCase): diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py index d0b2c52dc0..f30462f897 100644 --- a/exercises/practice/poker/poker_test.py +++ b/exercises/practice/poker/poker_test.py @@ -4,7 +4,9 @@ best_hands, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/poker/canonical-data.json +# File last updated on 2023-07-14 class PokerTest(unittest.TestCase): diff --git a/exercises/practice/pov/pov_test.py b/exercises/practice/pov/pov_test.py index 4bbdbaa871..93b622d72a 100644 --- a/exercises/practice/pov/pov_test.py +++ b/exercises/practice/pov/pov_test.py @@ -4,7 +4,9 @@ Tree, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pov/canonical-data.json +# File last updated on 2023-07-14 class PovTest(unittest.TestCase): diff --git a/exercises/practice/prime-factors/prime_factors_test.py b/exercises/practice/prime-factors/prime_factors_test.py index 02b36e7a16..187c4f2f96 100644 --- a/exercises/practice/prime-factors/prime_factors_test.py +++ b/exercises/practice/prime-factors/prime_factors_test.py @@ -4,7 +4,9 @@ factors, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/prime-factors/canonical-data.json +# File last updated on 2023-07-14 class PrimeFactorsTest(unittest.TestCase): diff --git a/exercises/practice/protein-translation/protein_translation_test.py b/exercises/practice/protein-translation/protein_translation_test.py index 4ce2d1151b..464d7f0ddd 100644 --- a/exercises/practice/protein-translation/protein_translation_test.py +++ b/exercises/practice/protein-translation/protein_translation_test.py @@ -4,7 +4,9 @@ proteins, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json +# File last updated on 2023-07-14 class ProteinTranslationTest(unittest.TestCase): diff --git a/exercises/practice/proverb/proverb_test.py b/exercises/practice/proverb/proverb_test.py index 69905dadf4..674cc26dc0 100644 --- a/exercises/practice/proverb/proverb_test.py +++ b/exercises/practice/proverb/proverb_test.py @@ -4,7 +4,9 @@ proverb, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/proverb/canonical-data.json +# File last updated on 2023-07-14 # PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.** # A new line in a result list below **does not** always equal a new list element. # Check comma placement carefully! diff --git a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py index 3536908ae8..b4b89e2d8a 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py +++ b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py @@ -4,7 +4,9 @@ triplets_with_sum, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pythagorean-triplet/canonical-data.json +# File last updated on 2023-07-14 class PythagoreanTripletTest(unittest.TestCase): diff --git a/exercises/practice/queen-attack/queen_attack_test.py b/exercises/practice/queen-attack/queen_attack_test.py index 34c212af19..52f994fc39 100644 --- a/exercises/practice/queen-attack/queen_attack_test.py +++ b/exercises/practice/queen-attack/queen_attack_test.py @@ -4,7 +4,9 @@ Queen, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json +# File last updated on 2023-07-14 class QueenAttackTest(unittest.TestCase): diff --git a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py index 873de5839b..fb546d4ab2 100644 --- a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py +++ b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py @@ -5,7 +5,9 @@ encode, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rail-fence-cipher/canonical-data.json +# File last updated on 2023-07-14 class RailFenceCipherTest(unittest.TestCase): diff --git a/exercises/practice/raindrops/raindrops_test.py b/exercises/practice/raindrops/raindrops_test.py index 7a5969c943..a196eb32af 100644 --- a/exercises/practice/raindrops/raindrops_test.py +++ b/exercises/practice/raindrops/raindrops_test.py @@ -4,7 +4,9 @@ convert, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/raindrops/canonical-data.json +# File last updated on 2023-07-14 class RaindropsTest(unittest.TestCase): diff --git a/exercises/practice/rational-numbers/rational_numbers_test.py b/exercises/practice/rational-numbers/rational_numbers_test.py index e7a50e528e..3b1e44a623 100644 --- a/exercises/practice/rational-numbers/rational_numbers_test.py +++ b/exercises/practice/rational-numbers/rational_numbers_test.py @@ -5,7 +5,9 @@ Rational, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rational-numbers/canonical-data.json +# File last updated on 2023-07-14 class RationalNumbersTest(unittest.TestCase): diff --git a/exercises/practice/react/react_test.py b/exercises/practice/react/react_test.py index 2ba86a80ff..5d4f22bcc6 100644 --- a/exercises/practice/react/react_test.py +++ b/exercises/practice/react/react_test.py @@ -7,7 +7,9 @@ ComputeCell, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/react/canonical-data.json +# File last updated on 2023-07-14 class ReactTest(unittest.TestCase): diff --git a/exercises/practice/rectangles/rectangles_test.py b/exercises/practice/rectangles/rectangles_test.py index 05938fc5b3..72cdae7cc2 100644 --- a/exercises/practice/rectangles/rectangles_test.py +++ b/exercises/practice/rectangles/rectangles_test.py @@ -4,7 +4,9 @@ rectangles, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rectangles/canonical-data.json +# File last updated on 2023-07-14 class RectanglesTest(unittest.TestCase): diff --git a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py index 72df473f07..9ace6d64b7 100644 --- a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py +++ b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py @@ -4,7 +4,9 @@ value, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-duo/canonical-data.json +# File last updated on 2023-07-14 class ResistorColorDuoTest(unittest.TestCase): diff --git a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py index ddfdfb6930..31901c5cfa 100644 --- a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py +++ b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py @@ -4,7 +4,9 @@ label, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-trio/canonical-data.json +# File last updated on 2023-07-14 class ResistorColorTrioTest(unittest.TestCase): diff --git a/exercises/practice/resistor-color/resistor_color_test.py b/exercises/practice/resistor-color/resistor_color_test.py index 631bc951e6..bb180385ce 100644 --- a/exercises/practice/resistor-color/resistor_color_test.py +++ b/exercises/practice/resistor-color/resistor_color_test.py @@ -5,7 +5,9 @@ colors, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color/canonical-data.json +# File last updated on 2023-07-14 class ResistorColorTest(unittest.TestCase): diff --git a/exercises/practice/rest-api/rest_api_test.py b/exercises/practice/rest-api/rest_api_test.py index 4a5c525fa8..4a00073dda 100644 --- a/exercises/practice/rest-api/rest_api_test.py +++ b/exercises/practice/rest-api/rest_api_test.py @@ -4,7 +4,9 @@ RestAPI, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rest-api/canonical-data.json +# File last updated on 2023-07-14 import json diff --git a/exercises/practice/reverse-string/reverse_string_test.py b/exercises/practice/reverse-string/reverse_string_test.py index a1a28dc1e6..cedc7f890c 100644 --- a/exercises/practice/reverse-string/reverse_string_test.py +++ b/exercises/practice/reverse-string/reverse_string_test.py @@ -4,7 +4,9 @@ reverse, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json +# File last updated on 2023-07-14 class ReverseStringTest(unittest.TestCase): diff --git a/exercises/practice/rna-transcription/rna_transcription_test.py b/exercises/practice/rna-transcription/rna_transcription_test.py index e6c88062bf..2e58d06b93 100644 --- a/exercises/practice/rna-transcription/rna_transcription_test.py +++ b/exercises/practice/rna-transcription/rna_transcription_test.py @@ -4,7 +4,9 @@ to_rna, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rna-transcription/canonical-data.json +# File last updated on 2023-07-14 class RnaTranscriptionTest(unittest.TestCase): diff --git a/exercises/practice/robot-simulator/robot_simulator_test.py b/exercises/practice/robot-simulator/robot_simulator_test.py index ffb5728c30..2d54517681 100644 --- a/exercises/practice/robot-simulator/robot_simulator_test.py +++ b/exercises/practice/robot-simulator/robot_simulator_test.py @@ -8,7 +8,9 @@ WEST, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/robot-simulator/canonical-data.json +# File last updated on 2023-07-14 class RobotSimulatorTest(unittest.TestCase): diff --git a/exercises/practice/roman-numerals/roman_numerals_test.py b/exercises/practice/roman-numerals/roman_numerals_test.py index 59e5e697fa..51ff27cb13 100644 --- a/exercises/practice/roman-numerals/roman_numerals_test.py +++ b/exercises/practice/roman-numerals/roman_numerals_test.py @@ -4,7 +4,9 @@ roman, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json +# File last updated on 2023-07-14 class RomanNumeralsTest(unittest.TestCase): def test_1_is_i(self): self.assertEqual(roman(1), "I") diff --git a/exercises/practice/rotational-cipher/rotational_cipher_test.py b/exercises/practice/rotational-cipher/rotational_cipher_test.py index b6e760a4ed..21dbcf8ab7 100644 --- a/exercises/practice/rotational-cipher/rotational_cipher_test.py +++ b/exercises/practice/rotational-cipher/rotational_cipher_test.py @@ -4,7 +4,9 @@ rotate, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rotational-cipher/canonical-data.json +# File last updated on 2023-07-14 class RotationalCipherTest(unittest.TestCase): diff --git a/exercises/practice/run-length-encoding/run_length_encoding_test.py b/exercises/practice/run-length-encoding/run_length_encoding_test.py index 9ac78f6d23..251c5f5ca8 100644 --- a/exercises/practice/run-length-encoding/run_length_encoding_test.py +++ b/exercises/practice/run-length-encoding/run_length_encoding_test.py @@ -5,7 +5,9 @@ decode, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json +# File last updated on 2023-07-14 class RunLengthEncodingTest(unittest.TestCase): diff --git a/exercises/practice/saddle-points/saddle_points_test.py b/exercises/practice/saddle-points/saddle_points_test.py index 24c18e09b7..4a33d1922b 100644 --- a/exercises/practice/saddle-points/saddle_points_test.py +++ b/exercises/practice/saddle-points/saddle_points_test.py @@ -11,7 +11,9 @@ saddle_points, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/saddle-points/canonical-data.json +# File last updated on 2023-07-14 def sorted_points(point_list): diff --git a/exercises/practice/satellite/satellite_test.py b/exercises/practice/satellite/satellite_test.py index 2eb329c2f9..c1629a9ce4 100644 --- a/exercises/practice/satellite/satellite_test.py +++ b/exercises/practice/satellite/satellite_test.py @@ -4,7 +4,9 @@ tree_from_traversals, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/satellite/canonical-data.json +# File last updated on 2023-07-14 class SatelliteTest(unittest.TestCase): diff --git a/exercises/practice/say/say_test.py b/exercises/practice/say/say_test.py index 22deb5364c..58f9a04a9d 100644 --- a/exercises/practice/say/say_test.py +++ b/exercises/practice/say/say_test.py @@ -4,7 +4,9 @@ say, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/say/canonical-data.json +# File last updated on 2023-07-14 class SayTest(unittest.TestCase): diff --git a/exercises/practice/scale-generator/scale_generator_test.py b/exercises/practice/scale-generator/scale_generator_test.py index 5757fad316..48f94f3d81 100644 --- a/exercises/practice/scale-generator/scale_generator_test.py +++ b/exercises/practice/scale-generator/scale_generator_test.py @@ -4,7 +4,9 @@ Scale, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/scale-generator/canonical-data.json +# File last updated on 2023-07-14 class ScaleGeneratorTest(unittest.TestCase): diff --git a/exercises/practice/scrabble-score/scrabble_score_test.py b/exercises/practice/scrabble-score/scrabble_score_test.py index 2ea8fd3240..1b95e627cb 100644 --- a/exercises/practice/scrabble-score/scrabble_score_test.py +++ b/exercises/practice/scrabble-score/scrabble_score_test.py @@ -4,7 +4,9 @@ score, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/scrabble-score/canonical-data.json +# File last updated on 2023-07-14 class ScrabbleScoreTest(unittest.TestCase): diff --git a/exercises/practice/secret-handshake/secret_handshake_test.py b/exercises/practice/secret-handshake/secret_handshake_test.py index b9e534d5fa..6abf64d452 100644 --- a/exercises/practice/secret-handshake/secret_handshake_test.py +++ b/exercises/practice/secret-handshake/secret_handshake_test.py @@ -4,7 +4,9 @@ commands, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/secret-handshake/canonical-data.json +# File last updated on 2023-07-14 class SecretHandshakeTest(unittest.TestCase): diff --git a/exercises/practice/series/series_test.py b/exercises/practice/series/series_test.py index 7f6de7a466..9c6ee8b8c1 100644 --- a/exercises/practice/series/series_test.py +++ b/exercises/practice/series/series_test.py @@ -4,7 +4,9 @@ slices, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/series/canonical-data.json +# File last updated on 2023-07-14 class SeriesTest(unittest.TestCase): diff --git a/exercises/practice/sgf-parsing/sgf_parsing_test.py b/exercises/practice/sgf-parsing/sgf_parsing_test.py index b3d43ed09d..f7acf2b182 100644 --- a/exercises/practice/sgf-parsing/sgf_parsing_test.py +++ b/exercises/practice/sgf-parsing/sgf_parsing_test.py @@ -5,7 +5,9 @@ SgfTree, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/sgf-parsing/canonical-data.json +# File last updated on 2023-07-14 class SgfParsingTest(unittest.TestCase): diff --git a/exercises/practice/sieve/sieve_test.py b/exercises/practice/sieve/sieve_test.py index 91bb00e7ae..487f402db6 100644 --- a/exercises/practice/sieve/sieve_test.py +++ b/exercises/practice/sieve/sieve_test.py @@ -4,7 +4,9 @@ primes, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/sieve/canonical-data.json +# File last updated on 2023-07-14 class SieveTest(unittest.TestCase): diff --git a/exercises/practice/simple-cipher/simple_cipher_test.py b/exercises/practice/simple-cipher/simple_cipher_test.py index 8b911d734b..af4e2540d0 100644 --- a/exercises/practice/simple-cipher/simple_cipher_test.py +++ b/exercises/practice/simple-cipher/simple_cipher_test.py @@ -5,7 +5,9 @@ Cipher, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/simple-cipher/canonical-data.json +# File last updated on 2023-07-14 class RandomKeyCipherTest(unittest.TestCase): diff --git a/exercises/practice/space-age/space_age_test.py b/exercises/practice/space-age/space_age_test.py index 1d248b19e3..d1dc13b517 100644 --- a/exercises/practice/space-age/space_age_test.py +++ b/exercises/practice/space-age/space_age_test.py @@ -4,7 +4,9 @@ SpaceAge, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/space-age/canonical-data.json +# File last updated on 2023-07-14 class SpaceAgeTest(unittest.TestCase): diff --git a/exercises/practice/spiral-matrix/spiral_matrix_test.py b/exercises/practice/spiral-matrix/spiral_matrix_test.py index 3e6658cf54..fe3d1be2b8 100644 --- a/exercises/practice/spiral-matrix/spiral_matrix_test.py +++ b/exercises/practice/spiral-matrix/spiral_matrix_test.py @@ -4,7 +4,9 @@ spiral_matrix, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/spiral-matrix/canonical-data.json +# File last updated on 2023-07-14 class SpiralMatrixTest(unittest.TestCase): diff --git a/exercises/practice/square-root/square_root_test.py b/exercises/practice/square-root/square_root_test.py index 635728fee4..1a8dda7ab2 100644 --- a/exercises/practice/square-root/square_root_test.py +++ b/exercises/practice/square-root/square_root_test.py @@ -4,7 +4,9 @@ square_root, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json +# File last updated on 2023-07-14 class SquareRootTest(unittest.TestCase): diff --git a/exercises/practice/sublist/sublist_test.py b/exercises/practice/sublist/sublist_test.py index b3dc9c7bce..dcabff2121 100644 --- a/exercises/practice/sublist/sublist_test.py +++ b/exercises/practice/sublist/sublist_test.py @@ -8,7 +8,9 @@ UNEQUAL, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/sublist/canonical-data.json +# File last updated on 2023-07-14 class SublistTest(unittest.TestCase): diff --git a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py index 29d0ac7244..762840fbbc 100644 --- a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py +++ b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py @@ -4,7 +4,9 @@ sum_of_multiples, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/sum-of-multiples/canonical-data.json +# File last updated on 2023-07-14 class SumOfMultiplesTest(unittest.TestCase): diff --git a/exercises/practice/tournament/tournament_test.py b/exercises/practice/tournament/tournament_test.py index 22a8166789..da8892d7df 100644 --- a/exercises/practice/tournament/tournament_test.py +++ b/exercises/practice/tournament/tournament_test.py @@ -4,7 +4,9 @@ tally, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/tournament/canonical-data.json +# File last updated on 2023-07-14 class TournamentTest(unittest.TestCase): diff --git a/exercises/practice/transpose/transpose_test.py b/exercises/practice/transpose/transpose_test.py index 0a0f5c024f..31464c6c6e 100644 --- a/exercises/practice/transpose/transpose_test.py +++ b/exercises/practice/transpose/transpose_test.py @@ -4,7 +4,9 @@ transpose, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json +# File last updated on 2023-07-14 class TransposeTest(unittest.TestCase): diff --git a/exercises/practice/triangle/triangle_test.py b/exercises/practice/triangle/triangle_test.py index f67d63fa69..7de4b34046 100644 --- a/exercises/practice/triangle/triangle_test.py +++ b/exercises/practice/triangle/triangle_test.py @@ -6,7 +6,9 @@ scalene, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json +# File last updated on 2023-07-14 class EquilateralTriangleTest(unittest.TestCase): diff --git a/exercises/practice/twelve-days/twelve_days_test.py b/exercises/practice/twelve-days/twelve_days_test.py index ec097cddb9..37076cf3ef 100644 --- a/exercises/practice/twelve-days/twelve_days_test.py +++ b/exercises/practice/twelve-days/twelve_days_test.py @@ -4,7 +4,9 @@ recite, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/twelve-days/canonical-data.json +# File last updated on 2023-07-14 # PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.** # A new line in a result list below **does not** always equal a new list element. # Check comma placement carefully! diff --git a/exercises/practice/two-bucket/two_bucket_test.py b/exercises/practice/two-bucket/two_bucket_test.py index 7fe7fac1a7..7ccf413573 100644 --- a/exercises/practice/two-bucket/two_bucket_test.py +++ b/exercises/practice/two-bucket/two_bucket_test.py @@ -4,7 +4,9 @@ measure, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/two-bucket/canonical-data.json +# File last updated on 2023-07-14 class TwoBucketTest(unittest.TestCase): diff --git a/exercises/practice/two-fer/two_fer_test.py b/exercises/practice/two-fer/two_fer_test.py index ea74f1165d..ae322272cb 100644 --- a/exercises/practice/two-fer/two_fer_test.py +++ b/exercises/practice/two-fer/two_fer_test.py @@ -4,7 +4,9 @@ two_fer, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/two-fer/canonical-data.json +# File last updated on 2023-07-14 class TwoFerTest(unittest.TestCase): diff --git a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py index c6867dac90..5f75ec9215 100644 --- a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py +++ b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py @@ -5,7 +5,9 @@ encode, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json +# File last updated on 2023-07-14 class VariableLengthQuantityTest(unittest.TestCase): diff --git a/exercises/practice/word-count/word_count_test.py b/exercises/practice/word-count/word_count_test.py index 329301a38e..783eca5a0b 100644 --- a/exercises/practice/word-count/word_count_test.py +++ b/exercises/practice/word-count/word_count_test.py @@ -4,7 +4,9 @@ count_words, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/word-count/canonical-data.json +# File last updated on 2023-07-14 class WordCountTest(unittest.TestCase): diff --git a/exercises/practice/word-search/word_search_test.py b/exercises/practice/word-search/word_search_test.py index e5a32ceed7..2dd3d43415 100644 --- a/exercises/practice/word-search/word_search_test.py +++ b/exercises/practice/word-search/word_search_test.py @@ -5,7 +5,9 @@ Point, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/word-search/canonical-data.json +# File last updated on 2023-07-14 class WordSearchTest(unittest.TestCase): diff --git a/exercises/practice/wordy/wordy_test.py b/exercises/practice/wordy/wordy_test.py index 3f85ed41f1..678985ff89 100644 --- a/exercises/practice/wordy/wordy_test.py +++ b/exercises/practice/wordy/wordy_test.py @@ -4,7 +4,9 @@ answer, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json +# File last updated on 2023-07-14 class WordyTest(unittest.TestCase): diff --git a/exercises/practice/yacht/yacht_test.py b/exercises/practice/yacht/yacht_test.py index 32b929942c..cf7d72b455 100644 --- a/exercises/practice/yacht/yacht_test.py +++ b/exercises/practice/yacht/yacht_test.py @@ -2,7 +2,9 @@ import yacht -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/yacht/canonical-data.json +# File last updated on 2023-07-14 class YachtTest(unittest.TestCase): diff --git a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py index 4b35afb7a7..f6ea9c82a3 100644 --- a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py +++ b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py @@ -5,7 +5,9 @@ owns_zebra, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/zebra-puzzle/canonical-data.json +# File last updated on 2023-07-14 class ZebraPuzzleTest(unittest.TestCase): diff --git a/exercises/practice/zipper/zipper_test.py b/exercises/practice/zipper/zipper_test.py index d770c92b3e..c3f3de0f6d 100644 --- a/exercises/practice/zipper/zipper_test.py +++ b/exercises/practice/zipper/zipper_test.py @@ -4,7 +4,9 @@ Zipper, ) -# Tests adapted from `problem-specifications//canonical-data.json` +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/zipper/canonical-data.json +# File last updated on 2023-07-14 class ZipperTest(unittest.TestCase): From b49664ac66964f01b1af0da86f34dc7406e8d5a1 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 15 Jul 2023 15:27:31 -0700 Subject: [PATCH 496/826] [Tooling]: Changed Generate Script to Skip Diffing Generated Comments & Timestamp (#3466) * Changed generate script to skp diffing generated comments and timestamp at top of file. * Reordered and regenerated test files for practice exercises. [no important files changed] --- bin/generate_tests.py | 4 ++++ config/generator_macros.j2 | 4 ++-- exercises/practice/acronym/.meta/template.j2 | 1 + exercises/practice/acronym/acronym_test.py | 8 ++++---- exercises/practice/affine-cipher/affine_cipher_test.py | 8 ++++---- exercises/practice/all-your-base/all_your_base_test.py | 8 ++++---- exercises/practice/allergies/allergies_test.py | 8 ++++---- exercises/practice/alphametics/alphametics_test.py | 8 ++++---- exercises/practice/anagram/anagram_test.py | 8 ++++---- .../practice/armstrong-numbers/armstrong_numbers_test.py | 8 ++++---- exercises/practice/atbash-cipher/atbash_cipher_test.py | 8 ++++---- exercises/practice/bank-account/bank_account_test.py | 8 ++++---- exercises/practice/beer-song/beer_song_test.py | 8 ++++---- .../binary-search-tree/binary_search_tree_test.py | 8 ++++---- exercises/practice/binary-search/binary_search_test.py | 8 ++++---- exercises/practice/bob/bob_test.py | 8 ++++---- exercises/practice/book-store/book_store_test.py | 8 ++++---- exercises/practice/bottle-song/bottle_song_test.py | 8 ++++---- exercises/practice/bowling/bowling_test.py | 8 ++++---- exercises/practice/change/change_test.py | 8 ++++---- .../practice/circular-buffer/circular_buffer_test.py | 8 ++++---- exercises/practice/clock/clock_test.py | 8 ++++---- .../collatz-conjecture/collatz_conjecture_test.py | 8 ++++---- .../practice/complex-numbers/complex_numbers_test.py | 8 ++++---- exercises/practice/connect/connect_test.py | 8 ++++---- exercises/practice/crypto-square/crypto_square_test.py | 8 ++++---- exercises/practice/custom-set/custom_set_test.py | 8 ++++---- exercises/practice/darts/darts_test.py | 8 ++++---- exercises/practice/diamond/diamond_test.py | 8 ++++---- .../difference-of-squares/difference_of_squares_test.py | 8 ++++---- exercises/practice/diffie-hellman/diffie_hellman_test.py | 8 ++++---- exercises/practice/dnd-character/dnd_character_test.py | 8 ++++---- exercises/practice/dominoes/dominoes_test.py | 8 ++++---- exercises/practice/etl/etl_test.py | 8 ++++---- exercises/practice/flatten-array/flatten_array_test.py | 8 ++++---- exercises/practice/food-chain/food_chain_test.py | 8 ++++---- exercises/practice/forth/forth_test.py | 8 ++++---- exercises/practice/gigasecond/gigasecond_test.py | 9 +++++---- exercises/practice/go-counting/go_counting_test.py | 8 ++++---- exercises/practice/grade-school/grade_school_test.py | 8 ++++---- exercises/practice/grains/grains_test.py | 8 ++++---- exercises/practice/grep/grep_test.py | 8 ++++---- exercises/practice/hamming/hamming_test.py | 8 ++++---- exercises/practice/high-scores/high_scores_test.py | 8 ++++---- exercises/practice/house/house_test.py | 8 ++++---- exercises/practice/isbn-verifier/isbn_verifier_test.py | 8 ++++---- exercises/practice/isogram/isogram_test.py | 8 ++++---- .../killer-sudoku-helper/killer_sudoku_helper_test.py | 8 ++++---- .../kindergarten-garden/kindergarten_garden_test.py | 8 ++++---- exercises/practice/knapsack/knapsack_test.py | 8 ++++---- .../largest_series_product_test.py | 8 ++++---- exercises/practice/leap/leap_test.py | 8 ++++---- exercises/practice/ledger/ledger_test.py | 8 ++++---- exercises/practice/linked-list/linked_list_test.py | 8 ++++---- exercises/practice/list-ops/list_ops_test.py | 8 ++++---- exercises/practice/luhn/luhn_test.py | 8 ++++---- exercises/practice/markdown/markdown_test.py | 8 ++++---- .../practice/matching-brackets/matching_brackets_test.py | 8 ++++---- exercises/practice/matrix/matrix_test.py | 8 ++++---- exercises/practice/meetup/meetup_test.py | 9 +++++---- exercises/practice/minesweeper/minesweeper_test.py | 8 ++++---- exercises/practice/nth-prime/nth_prime_test.py | 8 ++++---- exercises/practice/ocr-numbers/ocr_numbers_test.py | 8 ++++---- .../palindrome-products/palindrome_products_test.py | 8 ++++---- exercises/practice/pangram/pangram_test.py | 8 ++++---- .../practice/pascals-triangle/pascals_triangle_test.py | 9 +++++---- .../practice/perfect-numbers/perfect_numbers_test.py | 8 ++++---- exercises/practice/phone-number/phone_number_test.py | 8 ++++---- exercises/practice/pig-latin/pig_latin_test.py | 8 ++++---- exercises/practice/poker/poker_test.py | 8 ++++---- exercises/practice/pov/pov_test.py | 8 ++++---- exercises/practice/prime-factors/prime_factors_test.py | 8 ++++---- .../protein-translation/protein_translation_test.py | 8 ++++---- exercises/practice/proverb/proverb_test.py | 7 ++++--- .../pythagorean-triplet/pythagorean_triplet_test.py | 8 ++++---- exercises/practice/queen-attack/queen_attack_test.py | 8 ++++---- .../practice/rail-fence-cipher/rail_fence_cipher_test.py | 8 ++++---- exercises/practice/raindrops/raindrops_test.py | 8 ++++---- .../practice/rational-numbers/rational_numbers_test.py | 9 +++++---- exercises/practice/react/react_test.py | 8 ++++---- exercises/practice/rectangles/rectangles_test.py | 8 ++++---- .../resistor-color-duo/resistor_color_duo_test.py | 8 ++++---- .../resistor-color-trio/resistor_color_trio_test.py | 8 ++++---- exercises/practice/resistor-color/resistor_color_test.py | 8 ++++---- exercises/practice/rest-api/rest_api_test.py | 8 ++++---- exercises/practice/reverse-string/reverse_string_test.py | 8 ++++---- .../practice/rna-transcription/rna_transcription_test.py | 8 ++++---- .../practice/robot-simulator/robot_simulator_test.py | 8 ++++---- exercises/practice/roman-numerals/roman_numerals_test.py | 8 +++++--- .../practice/rotational-cipher/rotational_cipher_test.py | 8 ++++---- .../run-length-encoding/run_length_encoding_test.py | 8 ++++---- exercises/practice/saddle-points/saddle_points_test.py | 8 ++++---- exercises/practice/satellite/satellite_test.py | 8 ++++---- exercises/practice/say/say_test.py | 8 ++++---- .../practice/scale-generator/scale_generator_test.py | 8 ++++---- exercises/practice/scrabble-score/scrabble_score_test.py | 8 ++++---- .../practice/secret-handshake/secret_handshake_test.py | 8 ++++---- exercises/practice/series/series_test.py | 8 ++++---- exercises/practice/sgf-parsing/sgf_parsing_test.py | 8 ++++---- exercises/practice/sieve/sieve_test.py | 8 ++++---- exercises/practice/simple-cipher/simple_cipher_test.py | 9 +++++---- exercises/practice/space-age/space_age_test.py | 8 ++++---- exercises/practice/spiral-matrix/spiral_matrix_test.py | 8 ++++---- exercises/practice/square-root/square_root_test.py | 8 ++++---- exercises/practice/sublist/sublist_test.py | 8 ++++---- .../practice/sum-of-multiples/sum_of_multiples_test.py | 8 ++++---- exercises/practice/tournament/tournament_test.py | 8 ++++---- exercises/practice/transpose/transpose_test.py | 8 ++++---- exercises/practice/triangle/triangle_test.py | 8 ++++---- exercises/practice/twelve-days/twelve_days_test.py | 7 ++++--- exercises/practice/two-bucket/two_bucket_test.py | 8 ++++---- exercises/practice/two-fer/two_fer_test.py | 8 ++++---- .../variable_length_quantity_test.py | 8 ++++---- exercises/practice/word-count/word_count_test.py | 8 ++++---- exercises/practice/word-search/word_search_test.py | 8 ++++---- exercises/practice/wordy/wordy_test.py | 8 ++++---- exercises/practice/yacht/yacht_test.py | 2 +- exercises/practice/zebra-puzzle/zebra_puzzle_test.py | 8 ++++---- exercises/practice/zipper/zipper_test.py | 8 ++++---- 119 files changed, 474 insertions(+), 460 deletions(-) diff --git a/bin/generate_tests.py b/bin/generate_tests.py index d5fc4c5c5e..285c88d2e7 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -271,8 +271,12 @@ def check_template(slug: str, tests_path: Path, tmpfile: Path): check_ok = False if check_ok and not filecmp.cmp(tmpfile, tests_path): with tests_path.open() as f: + for line in range(5): + next(f) current_lines = f.readlines() with tmpfile.open() as f: + for line in range(4): + next(f) rendered_lines = f.readlines() diff = difflib.unified_diff( current_lines, diff --git a/config/generator_macros.j2 b/config/generator_macros.j2 index 32af5d5c9f..37d8e45a80 100644 --- a/config/generator_macros.j2 +++ b/config/generator_macros.j2 @@ -16,6 +16,8 @@ {%- endmacro %} {% macro header(imports=[], ignore=[]) -%} +{{ canonical_ref() }} + import unittest from {{ exercise | to_snake }} import ({% if imports -%} @@ -29,8 +31,6 @@ from {{ exercise | to_snake }} import ({% if imports -%} {%- endif -%} {% endfor %} {%- endif %}) - -{{ canonical_ref() }} {%- endmacro %} {% macro utility() -%}# Utility functions diff --git a/exercises/practice/acronym/.meta/template.j2 b/exercises/practice/acronym/.meta/template.j2 index 70c5d431ca..c2e35812b1 100644 --- a/exercises/practice/acronym/.meta/template.j2 +++ b/exercises/practice/acronym/.meta/template.j2 @@ -1,4 +1,5 @@ {%- import "generator_macros.j2" as macros with context -%} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): diff --git a/exercises/practice/acronym/acronym_test.py b/exercises/practice/acronym/acronym_test.py index 444f60cb55..78557dd88b 100644 --- a/exercises/practice/acronym/acronym_test.py +++ b/exercises/practice/acronym/acronym_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json +# File last updated on 2023-07-15 + import unittest from acronym import ( abbreviate, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json -# File last updated on 2023-07-14 - class AcronymTest(unittest.TestCase): def test_basic(self): diff --git a/exercises/practice/affine-cipher/affine_cipher_test.py b/exercises/practice/affine-cipher/affine_cipher_test.py index 713e391023..06e6faf0da 100644 --- a/exercises/practice/affine-cipher/affine_cipher_test.py +++ b/exercises/practice/affine-cipher/affine_cipher_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json +# File last updated on 2023-07-15 + import unittest from affine_cipher import ( @@ -5,10 +9,6 @@ encode, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json -# File last updated on 2023-07-14 - class AffineCipherTest(unittest.TestCase): def test_encode_yes(self): diff --git a/exercises/practice/all-your-base/all_your_base_test.py b/exercises/practice/all-your-base/all_your_base_test.py index 182237e4e1..9a4ca40ccd 100644 --- a/exercises/practice/all-your-base/all_your_base_test.py +++ b/exercises/practice/all-your-base/all_your_base_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json +# File last updated on 2023-07-15 + import unittest from all_your_base import ( rebase, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json -# File last updated on 2023-07-14 - class AllYourBaseTest(unittest.TestCase): def test_single_bit_one_to_decimal(self): diff --git a/exercises/practice/allergies/allergies_test.py b/exercises/practice/allergies/allergies_test.py index fa4e1c55ee..8d6d574e20 100644 --- a/exercises/practice/allergies/allergies_test.py +++ b/exercises/practice/allergies/allergies_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json +# File last updated on 2023-07-15 + import unittest from allergies import ( Allergies, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json -# File last updated on 2023-07-14 - class AllergiesTest(unittest.TestCase): def test_eggs_not_allergic_to_anything(self): diff --git a/exercises/practice/alphametics/alphametics_test.py b/exercises/practice/alphametics/alphametics_test.py index 5557b3c8eb..014df6ceb9 100644 --- a/exercises/practice/alphametics/alphametics_test.py +++ b/exercises/practice/alphametics/alphametics_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json +# File last updated on 2023-07-15 + import unittest from alphametics import ( solve, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json -# File last updated on 2023-07-14 - class AlphameticsTest(unittest.TestCase): def test_puzzle_with_three_letters(self): diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py index 85087e8580..69145b9d4f 100644 --- a/exercises/practice/anagram/anagram_test.py +++ b/exercises/practice/anagram/anagram_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json +# File last updated on 2023-07-15 + import unittest from anagram import ( find_anagrams, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2023-07-14 - class AnagramTest(unittest.TestCase): def test_no_matches(self): diff --git a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py index ed6907d884..49831704f0 100644 --- a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py +++ b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json +# File last updated on 2023-07-15 + import unittest from armstrong_numbers import ( is_armstrong_number, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json -# File last updated on 2023-07-14 - class ArmstrongNumbersTest(unittest.TestCase): def test_zero_is_an_armstrong_number(self): diff --git a/exercises/practice/atbash-cipher/atbash_cipher_test.py b/exercises/practice/atbash-cipher/atbash_cipher_test.py index 4e630e3a12..dded351c58 100644 --- a/exercises/practice/atbash-cipher/atbash_cipher_test.py +++ b/exercises/practice/atbash-cipher/atbash_cipher_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/atbash-cipher/canonical-data.json +# File last updated on 2023-07-15 + import unittest from atbash_cipher import ( @@ -5,10 +9,6 @@ encode, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/atbash-cipher/canonical-data.json -# File last updated on 2023-07-14 - class AtbashCipherTest(unittest.TestCase): def test_encode_yes(self): diff --git a/exercises/practice/bank-account/bank_account_test.py b/exercises/practice/bank-account/bank_account_test.py index 9e974da8fa..d58663395b 100644 --- a/exercises/practice/bank-account/bank_account_test.py +++ b/exercises/practice/bank-account/bank_account_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/bank-account/canonical-data.json +# File last updated on 2023-07-15 + import unittest from bank_account import ( BankAccount, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/bank-account/canonical-data.json -# File last updated on 2023-07-14 - class BankAccountTest(unittest.TestCase): def test_newly_opened_account_has_zero_balance(self): diff --git a/exercises/practice/beer-song/beer_song_test.py b/exercises/practice/beer-song/beer_song_test.py index b11f11d1f5..c1391989fa 100644 --- a/exercises/practice/beer-song/beer_song_test.py +++ b/exercises/practice/beer-song/beer_song_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/beer-song/canonical-data.json +# File last updated on 2023-07-15 + import unittest from beer_song import ( recite, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/beer-song/canonical-data.json -# File last updated on 2023-07-14 - class BeerSongTest(unittest.TestCase): def test_first_generic_verse(self): diff --git a/exercises/practice/binary-search-tree/binary_search_tree_test.py b/exercises/practice/binary-search-tree/binary_search_tree_test.py index 479cd3411f..fdb915fe77 100644 --- a/exercises/practice/binary-search-tree/binary_search_tree_test.py +++ b/exercises/practice/binary-search-tree/binary_search_tree_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search-tree/canonical-data.json +# File last updated on 2023-07-15 + import unittest from binary_search_tree import ( @@ -5,10 +9,6 @@ TreeNode, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search-tree/canonical-data.json -# File last updated on 2023-07-14 - class BinarySearchTreeTest(unittest.TestCase): def test_data_is_retained(self): diff --git a/exercises/practice/binary-search/binary_search_test.py b/exercises/practice/binary-search/binary_search_test.py index 325622ad37..6d09497734 100644 --- a/exercises/practice/binary-search/binary_search_test.py +++ b/exercises/practice/binary-search/binary_search_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search/canonical-data.json +# File last updated on 2023-07-15 + import unittest from binary_search import ( find, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search/canonical-data.json -# File last updated on 2023-07-14 - class BinarySearchTest(unittest.TestCase): def test_finds_a_value_in_an_array_with_one_element(self): diff --git a/exercises/practice/bob/bob_test.py b/exercises/practice/bob/bob_test.py index 67c7f64f7d..089636fad5 100644 --- a/exercises/practice/bob/bob_test.py +++ b/exercises/practice/bob/bob_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json +# File last updated on 2023-07-15 + import unittest from bob import ( response, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json -# File last updated on 2023-07-14 - class BobTest(unittest.TestCase): def test_stating_something(self): diff --git a/exercises/practice/book-store/book_store_test.py b/exercises/practice/book-store/book_store_test.py index 329e5410f2..66c1b72310 100644 --- a/exercises/practice/book-store/book_store_test.py +++ b/exercises/practice/book-store/book_store_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/book-store/canonical-data.json +# File last updated on 2023-07-15 + import unittest from book_store import ( total, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/book-store/canonical-data.json -# File last updated on 2023-07-14 - class BookStoreTest(unittest.TestCase): def test_only_a_single_book(self): diff --git a/exercises/practice/bottle-song/bottle_song_test.py b/exercises/practice/bottle-song/bottle_song_test.py index f8c33aac2b..487a46cedc 100644 --- a/exercises/practice/bottle-song/bottle_song_test.py +++ b/exercises/practice/bottle-song/bottle_song_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/bottle-song/canonical-data.json +# File last updated on 2023-07-15 + import unittest from bottle_song import ( recite, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/bottle-song/canonical-data.json -# File last updated on 2023-07-14 - class BottleSongTest(unittest.TestCase): def test_first_generic_verse(self): diff --git a/exercises/practice/bowling/bowling_test.py b/exercises/practice/bowling/bowling_test.py index 8e04a4c814..374096e592 100644 --- a/exercises/practice/bowling/bowling_test.py +++ b/exercises/practice/bowling/bowling_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json +# File last updated on 2023-07-15 + import unittest from bowling import ( BowlingGame, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json -# File last updated on 2023-07-14 - class BowlingTest(unittest.TestCase): def roll_new_game(self, rolls): diff --git a/exercises/practice/change/change_test.py b/exercises/practice/change/change_test.py index 74fead1196..b090a3965f 100644 --- a/exercises/practice/change/change_test.py +++ b/exercises/practice/change/change_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json +# File last updated on 2023-07-15 + import unittest from change import ( find_fewest_coins, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json -# File last updated on 2023-07-14 - class ChangeTest(unittest.TestCase): def test_change_for_1_cent(self): diff --git a/exercises/practice/circular-buffer/circular_buffer_test.py b/exercises/practice/circular-buffer/circular_buffer_test.py index 411df16bbd..b96a25e15c 100644 --- a/exercises/practice/circular-buffer/circular_buffer_test.py +++ b/exercises/practice/circular-buffer/circular_buffer_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/circular-buffer/canonical-data.json +# File last updated on 2023-07-15 + import unittest from circular_buffer import ( @@ -6,10 +10,6 @@ BufferFullException, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/circular-buffer/canonical-data.json -# File last updated on 2023-07-14 - class CircularBufferTest(unittest.TestCase): def test_reading_empty_buffer_should_fail(self): diff --git a/exercises/practice/clock/clock_test.py b/exercises/practice/clock/clock_test.py index 4fb17b4b1e..db4bc919cf 100644 --- a/exercises/practice/clock/clock_test.py +++ b/exercises/practice/clock/clock_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/clock/canonical-data.json +# File last updated on 2023-07-15 + import unittest from clock import ( Clock, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/clock/canonical-data.json -# File last updated on 2023-07-14 - class ClockTest(unittest.TestCase): # Create A String Representation diff --git a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py index 36c386ee39..d18bc37719 100644 --- a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py +++ b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json +# File last updated on 2023-07-15 + import unittest from collatz_conjecture import ( steps, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json -# File last updated on 2023-07-14 - class CollatzConjectureTest(unittest.TestCase): def test_zero_steps_for_one(self): diff --git a/exercises/practice/complex-numbers/complex_numbers_test.py b/exercises/practice/complex-numbers/complex_numbers_test.py index 995439763a..637eb7b90d 100644 --- a/exercises/practice/complex-numbers/complex_numbers_test.py +++ b/exercises/practice/complex-numbers/complex_numbers_test.py @@ -1,15 +1,15 @@ import math +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json +# File last updated on 2023-07-15 + import unittest from complex_numbers import ( ComplexNumber, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json -# File last updated on 2023-07-14 - class ComplexNumbersTest(unittest.TestCase): diff --git a/exercises/practice/connect/connect_test.py b/exercises/practice/connect/connect_test.py index a5b6f32444..239944a5cb 100644 --- a/exercises/practice/connect/connect_test.py +++ b/exercises/practice/connect/connect_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json +# File last updated on 2023-07-15 + import unittest from connect import ( ConnectGame, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json -# File last updated on 2023-07-14 - class ConnectTest(unittest.TestCase): def test_an_empty_board_has_no_winner(self): diff --git a/exercises/practice/crypto-square/crypto_square_test.py b/exercises/practice/crypto-square/crypto_square_test.py index c2845ef562..08cfa87c7b 100644 --- a/exercises/practice/crypto-square/crypto_square_test.py +++ b/exercises/practice/crypto-square/crypto_square_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json +# File last updated on 2023-07-15 + import unittest from crypto_square import ( cipher_text, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json -# File last updated on 2023-07-14 - class CryptoSquareTest(unittest.TestCase): def test_empty_plaintext_results_in_an_empty_ciphertext(self): diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index ecad09fdba..5e6ab124be 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json +# File last updated on 2023-07-15 + import unittest from custom_set import ( CustomSet, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2023-07-14 - class CustomSetTest(unittest.TestCase): def test_sets_with_no_elements_are_empty(self): diff --git a/exercises/practice/darts/darts_test.py b/exercises/practice/darts/darts_test.py index 9f9a6a323e..c417dabd98 100644 --- a/exercises/practice/darts/darts_test.py +++ b/exercises/practice/darts/darts_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/darts/canonical-data.json +# File last updated on 2023-07-15 + import unittest from darts import ( score, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/darts/canonical-data.json -# File last updated on 2023-07-14 - class DartsTest(unittest.TestCase): def test_missed_target(self): diff --git a/exercises/practice/diamond/diamond_test.py b/exercises/practice/diamond/diamond_test.py index 335b0bd799..504380af42 100644 --- a/exercises/practice/diamond/diamond_test.py +++ b/exercises/practice/diamond/diamond_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/diamond/canonical-data.json +# File last updated on 2023-07-15 + import unittest from diamond import ( rows, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/diamond/canonical-data.json -# File last updated on 2023-07-14 - class DiamondTest(unittest.TestCase): def test_degenerate_case_with_a_single_a_row(self): diff --git a/exercises/practice/difference-of-squares/difference_of_squares_test.py b/exercises/practice/difference-of-squares/difference_of_squares_test.py index d38681f742..d75e565bef 100644 --- a/exercises/practice/difference-of-squares/difference_of_squares_test.py +++ b/exercises/practice/difference-of-squares/difference_of_squares_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/difference-of-squares/canonical-data.json +# File last updated on 2023-07-15 + import unittest from difference_of_squares import ( @@ -6,10 +10,6 @@ sum_of_squares, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/difference-of-squares/canonical-data.json -# File last updated on 2023-07-14 - class DifferenceOfSquaresTest(unittest.TestCase): def test_square_of_sum_1(self): diff --git a/exercises/practice/diffie-hellman/diffie_hellman_test.py b/exercises/practice/diffie-hellman/diffie_hellman_test.py index 60c514d9f8..ce5819821c 100644 --- a/exercises/practice/diffie-hellman/diffie_hellman_test.py +++ b/exercises/practice/diffie-hellman/diffie_hellman_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/diffie-hellman/canonical-data.json +# File last updated on 2023-07-15 + import unittest from diffie_hellman import ( @@ -6,10 +10,6 @@ secret, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/diffie-hellman/canonical-data.json -# File last updated on 2023-07-14 - class DiffieHellmanTest(unittest.TestCase): def test_private_key_is_greater_than_1_and_less_than_p(self): diff --git a/exercises/practice/dnd-character/dnd_character_test.py b/exercises/practice/dnd-character/dnd_character_test.py index d97cbfda62..f158f257a8 100644 --- a/exercises/practice/dnd-character/dnd_character_test.py +++ b/exercises/practice/dnd-character/dnd_character_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/dnd-character/canonical-data.json +# File last updated on 2023-07-15 + import unittest from dnd_character import ( @@ -5,10 +9,6 @@ modifier, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/dnd-character/canonical-data.json -# File last updated on 2023-07-14 - class DndCharacterTest(unittest.TestCase): def test_ability_modifier_for_score_3_is_n4(self): diff --git a/exercises/practice/dominoes/dominoes_test.py b/exercises/practice/dominoes/dominoes_test.py index a9d82512c2..78befc761b 100644 --- a/exercises/practice/dominoes/dominoes_test.py +++ b/exercises/practice/dominoes/dominoes_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json +# File last updated on 2023-07-15 + import unittest from dominoes import ( can_chain, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json -# File last updated on 2023-07-14 - class DominoesTest(unittest.TestCase): def test_empty_input_empty_output(self): diff --git a/exercises/practice/etl/etl_test.py b/exercises/practice/etl/etl_test.py index b2ce956fe4..d8e587bc79 100644 --- a/exercises/practice/etl/etl_test.py +++ b/exercises/practice/etl/etl_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/etl/canonical-data.json +# File last updated on 2023-07-15 + import unittest from etl import ( transform, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/etl/canonical-data.json -# File last updated on 2023-07-14 - class EtlTest(unittest.TestCase): def test_single_letter(self): diff --git a/exercises/practice/flatten-array/flatten_array_test.py b/exercises/practice/flatten-array/flatten_array_test.py index 8271ce1d06..5d73db6b6e 100644 --- a/exercises/practice/flatten-array/flatten_array_test.py +++ b/exercises/practice/flatten-array/flatten_array_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/flatten-array/canonical-data.json +# File last updated on 2023-07-15 + import unittest from flatten_array import ( flatten, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/flatten-array/canonical-data.json -# File last updated on 2023-07-14 - class FlattenArrayTest(unittest.TestCase): def test_empty(self): diff --git a/exercises/practice/food-chain/food_chain_test.py b/exercises/practice/food-chain/food_chain_test.py index 195c1ab117..d330574c0b 100644 --- a/exercises/practice/food-chain/food_chain_test.py +++ b/exercises/practice/food-chain/food_chain_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/food-chain/canonical-data.json +# File last updated on 2023-07-15 + import unittest from food_chain import ( recite, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/food-chain/canonical-data.json -# File last updated on 2023-07-14 - class FoodChainTest(unittest.TestCase): def test_fly(self): diff --git a/exercises/practice/forth/forth_test.py b/exercises/practice/forth/forth_test.py index 07778ceb3a..458ce47656 100644 --- a/exercises/practice/forth/forth_test.py +++ b/exercises/practice/forth/forth_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json +# File last updated on 2023-07-15 + import unittest from forth import ( @@ -5,10 +9,6 @@ StackUnderflowError, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json -# File last updated on 2023-07-14 - class ForthTest(unittest.TestCase): def test_parsing_and_numbers_numbers_just_get_pushed_onto_the_stack(self): diff --git a/exercises/practice/gigasecond/gigasecond_test.py b/exercises/practice/gigasecond/gigasecond_test.py index c1e24d4bc4..74017949aa 100644 --- a/exercises/practice/gigasecond/gigasecond_test.py +++ b/exercises/practice/gigasecond/gigasecond_test.py @@ -1,14 +1,15 @@ from datetime import datetime + +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/gigasecond/canonical-data.json +# File last updated on 2023-07-15 + import unittest from gigasecond import ( add, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/gigasecond/canonical-data.json -# File last updated on 2023-07-14 - class GigasecondTest(unittest.TestCase): def test_date_only_specification_of_time(self): diff --git a/exercises/practice/go-counting/go_counting_test.py b/exercises/practice/go-counting/go_counting_test.py index e0920459cb..81886ca876 100644 --- a/exercises/practice/go-counting/go_counting_test.py +++ b/exercises/practice/go-counting/go_counting_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/go-counting/canonical-data.json +# File last updated on 2023-07-15 + import unittest from go_counting import ( @@ -7,10 +11,6 @@ NONE, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/go-counting/canonical-data.json -# File last updated on 2023-07-14 - class GoCountingTest(unittest.TestCase): def test_black_corner_territory_on_5x5_board(self): diff --git a/exercises/practice/grade-school/grade_school_test.py b/exercises/practice/grade-school/grade_school_test.py index 19509de653..2d2b4f5604 100644 --- a/exercises/practice/grade-school/grade_school_test.py +++ b/exercises/practice/grade-school/grade_school_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/grade-school/canonical-data.json +# File last updated on 2023-07-15 + import unittest from grade_school import ( School, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/grade-school/canonical-data.json -# File last updated on 2023-07-14 - class GradeSchoolTest(unittest.TestCase): def test_roster_is_empty_when_no_student_is_added(self): diff --git a/exercises/practice/grains/grains_test.py b/exercises/practice/grains/grains_test.py index 5a4de3a877..e9d72f6a46 100644 --- a/exercises/practice/grains/grains_test.py +++ b/exercises/practice/grains/grains_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json +# File last updated on 2023-07-15 + import unittest from grains import ( @@ -5,10 +9,6 @@ total, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json -# File last updated on 2023-07-14 - class GrainsTest(unittest.TestCase): def test_grains_on_square_1(self): diff --git a/exercises/practice/grep/grep_test.py b/exercises/practice/grep/grep_test.py index ff81e36d8d..aff476d239 100644 --- a/exercises/practice/grep/grep_test.py +++ b/exercises/practice/grep/grep_test.py @@ -1,12 +1,12 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/grep/canonical-data.json +# File last updated on 2023-07-15 + import unittest from grep import ( grep, ) - -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/grep/canonical-data.json -# File last updated on 2023-07-14 import io from unittest import mock diff --git a/exercises/practice/hamming/hamming_test.py b/exercises/practice/hamming/hamming_test.py index e9fcadea9e..d8d5c138a9 100644 --- a/exercises/practice/hamming/hamming_test.py +++ b/exercises/practice/hamming/hamming_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/hamming/canonical-data.json +# File last updated on 2023-07-15 + import unittest from hamming import ( distance, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/hamming/canonical-data.json -# File last updated on 2023-07-14 - class HammingTest(unittest.TestCase): def test_empty_strands(self): diff --git a/exercises/practice/high-scores/high_scores_test.py b/exercises/practice/high-scores/high_scores_test.py index ec716a489c..d1e4b71d6f 100644 --- a/exercises/practice/high-scores/high_scores_test.py +++ b/exercises/practice/high-scores/high_scores_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/high-scores/canonical-data.json +# File last updated on 2023-07-15 + import unittest from high_scores import ( HighScores, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/high-scores/canonical-data.json -# File last updated on 2023-07-14 - class HighScoresTest(unittest.TestCase): def test_list_of_scores(self): diff --git a/exercises/practice/house/house_test.py b/exercises/practice/house/house_test.py index 6ad16a0ccb..a42b395049 100644 --- a/exercises/practice/house/house_test.py +++ b/exercises/practice/house/house_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/house/canonical-data.json +# File last updated on 2023-07-15 + import unittest from house import ( recite, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/house/canonical-data.json -# File last updated on 2023-07-14 - class HouseTest(unittest.TestCase): def test_verse_one_the_house_that_jack_built(self): diff --git a/exercises/practice/isbn-verifier/isbn_verifier_test.py b/exercises/practice/isbn-verifier/isbn_verifier_test.py index aa3d954d22..22fdd0a9b7 100644 --- a/exercises/practice/isbn-verifier/isbn_verifier_test.py +++ b/exercises/practice/isbn-verifier/isbn_verifier_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/isbn-verifier/canonical-data.json +# File last updated on 2023-07-15 + import unittest from isbn_verifier import ( is_valid, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/isbn-verifier/canonical-data.json -# File last updated on 2023-07-14 - class IsbnVerifierTest(unittest.TestCase): def test_valid_isbn(self): diff --git a/exercises/practice/isogram/isogram_test.py b/exercises/practice/isogram/isogram_test.py index 99940e3ddf..b7427a7d44 100644 --- a/exercises/practice/isogram/isogram_test.py +++ b/exercises/practice/isogram/isogram_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/isogram/canonical-data.json +# File last updated on 2023-07-15 + import unittest from isogram import ( is_isogram, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/isogram/canonical-data.json -# File last updated on 2023-07-14 - class IsogramTest(unittest.TestCase): def test_empty_string(self): diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py index 3a7f4c0dcc..24b02c08fb 100644 --- a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/killer-sudoku-helper/canonical-data.json +# File last updated on 2023-07-15 + import unittest from killer_sudoku_helper import ( combinations, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/killer-sudoku-helper/canonical-data.json -# File last updated on 2023-07-14 - class KillerSudokuHelperTest(unittest.TestCase): def test_1(self): diff --git a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py index 5773dfa390..83d4978e14 100644 --- a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py +++ b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/kindergarten-garden/canonical-data.json +# File last updated on 2023-07-15 + import unittest from kindergarten_garden import ( Garden, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/kindergarten-garden/canonical-data.json -# File last updated on 2023-07-14 - class KindergartenGardenTest(unittest.TestCase): def test_partial_garden_garden_with_single_student(self): diff --git a/exercises/practice/knapsack/knapsack_test.py b/exercises/practice/knapsack/knapsack_test.py index 20429bb263..a412023784 100644 --- a/exercises/practice/knapsack/knapsack_test.py +++ b/exercises/practice/knapsack/knapsack_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json +# File last updated on 2023-07-15 + import unittest from knapsack import ( maximum_value, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json -# File last updated on 2023-07-14 - class KnapsackTest(unittest.TestCase): def test_no_items(self): diff --git a/exercises/practice/largest-series-product/largest_series_product_test.py b/exercises/practice/largest-series-product/largest_series_product_test.py index 8b02dfbbb7..e75f022d63 100644 --- a/exercises/practice/largest-series-product/largest_series_product_test.py +++ b/exercises/practice/largest-series-product/largest_series_product_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/largest-series-product/canonical-data.json +# File last updated on 2023-07-15 + import unittest from largest_series_product import ( largest_product, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/largest-series-product/canonical-data.json -# File last updated on 2023-07-14 - class LargestSeriesProductTest(unittest.TestCase): def test_finds_the_largest_product_if_span_equals_length(self): diff --git a/exercises/practice/leap/leap_test.py b/exercises/practice/leap/leap_test.py index 2b25b36a6a..d8c508de5e 100644 --- a/exercises/practice/leap/leap_test.py +++ b/exercises/practice/leap/leap_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json +# File last updated on 2023-07-15 + import unittest from leap import ( leap_year, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json -# File last updated on 2023-07-14 - class LeapTest(unittest.TestCase): def test_year_not_divisible_by_4_in_common_year(self): diff --git a/exercises/practice/ledger/ledger_test.py b/exercises/practice/ledger/ledger_test.py index 2c51a4d771..e8432bb518 100644 --- a/exercises/practice/ledger/ledger_test.py +++ b/exercises/practice/ledger/ledger_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/ledger/canonical-data.json +# File last updated on 2023-07-15 + import unittest from ledger import ( @@ -5,10 +9,6 @@ create_entry, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/ledger/canonical-data.json -# File last updated on 2023-07-14 - class LedgerTest(unittest.TestCase): maxDiff = 5000 diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index c0b44de4d9..9a196084e6 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/linked-list/canonical-data.json +# File last updated on 2023-07-15 + import unittest from linked_list import ( LinkedList, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/linked-list/canonical-data.json -# File last updated on 2023-07-14 - class LinkedListTest(unittest.TestCase): def test_pop_gets_element_from_the_list(self): diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index 7bd60f4f21..4bff0080b8 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/list-ops/canonical-data.json +# File last updated on 2023-07-15 + import unittest from list_ops import ( @@ -11,10 +15,6 @@ map as list_ops_map, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/list-ops/canonical-data.json -# File last updated on 2023-07-14 - class ListOpsTest(unittest.TestCase): def test_append_empty_lists(self): diff --git a/exercises/practice/luhn/luhn_test.py b/exercises/practice/luhn/luhn_test.py index dd309dd72f..f6b078cca3 100644 --- a/exercises/practice/luhn/luhn_test.py +++ b/exercises/practice/luhn/luhn_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/luhn/canonical-data.json +# File last updated on 2023-07-15 + import unittest from luhn import ( Luhn, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/luhn/canonical-data.json -# File last updated on 2023-07-14 - class LuhnTest(unittest.TestCase): def test_single_digit_strings_can_not_be_valid(self): diff --git a/exercises/practice/markdown/markdown_test.py b/exercises/practice/markdown/markdown_test.py index b79353cdd7..4501adb787 100644 --- a/exercises/practice/markdown/markdown_test.py +++ b/exercises/practice/markdown/markdown_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/markdown/canonical-data.json +# File last updated on 2023-07-15 + import unittest from markdown import ( parse, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/markdown/canonical-data.json -# File last updated on 2023-07-14 - class MarkdownTest(unittest.TestCase): def test_parses_normal_text_as_a_paragraph(self): diff --git a/exercises/practice/matching-brackets/matching_brackets_test.py b/exercises/practice/matching-brackets/matching_brackets_test.py index b83c341a29..ae59ead1fc 100644 --- a/exercises/practice/matching-brackets/matching_brackets_test.py +++ b/exercises/practice/matching-brackets/matching_brackets_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/matching-brackets/canonical-data.json +# File last updated on 2023-07-15 + import unittest from matching_brackets import ( is_paired, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/matching-brackets/canonical-data.json -# File last updated on 2023-07-14 - class MatchingBracketsTest(unittest.TestCase): def test_paired_square_brackets(self): diff --git a/exercises/practice/matrix/matrix_test.py b/exercises/practice/matrix/matrix_test.py index 8573ccf9a6..49e1ea9022 100644 --- a/exercises/practice/matrix/matrix_test.py +++ b/exercises/practice/matrix/matrix_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/matrix/canonical-data.json +# File last updated on 2023-07-15 + import unittest from matrix import ( Matrix, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/matrix/canonical-data.json -# File last updated on 2023-07-14 - class MatrixTest(unittest.TestCase): def test_extract_row_from_one_number_matrix(self): diff --git a/exercises/practice/meetup/meetup_test.py b/exercises/practice/meetup/meetup_test.py index a9b3721c03..24695176bd 100644 --- a/exercises/practice/meetup/meetup_test.py +++ b/exercises/practice/meetup/meetup_test.py @@ -1,4 +1,9 @@ from datetime import date + +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/meetup/canonical-data.json +# File last updated on 2023-07-15 + import unittest from meetup import ( @@ -6,10 +11,6 @@ MeetupDayException, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/meetup/canonical-data.json -# File last updated on 2023-07-14 - class MeetupTest(unittest.TestCase): def test_when_teenth_monday_is_the_13th_the_first_day_of_the_teenth_week(self): diff --git a/exercises/practice/minesweeper/minesweeper_test.py b/exercises/practice/minesweeper/minesweeper_test.py index 32927a95ef..763281d66e 100644 --- a/exercises/practice/minesweeper/minesweeper_test.py +++ b/exercises/practice/minesweeper/minesweeper_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/minesweeper/canonical-data.json +# File last updated on 2023-07-15 + import unittest from minesweeper import ( annotate, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/minesweeper/canonical-data.json -# File last updated on 2023-07-14 - class MinesweeperTest(unittest.TestCase): def test_no_rows(self): diff --git a/exercises/practice/nth-prime/nth_prime_test.py b/exercises/practice/nth-prime/nth_prime_test.py index 50a68a05e2..19c68e55f3 100644 --- a/exercises/practice/nth-prime/nth_prime_test.py +++ b/exercises/practice/nth-prime/nth_prime_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/nth-prime/canonical-data.json +# File last updated on 2023-07-15 + import unittest from nth_prime import ( prime, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/nth-prime/canonical-data.json -# File last updated on 2023-07-14 - def prime_range(n): """Returns a list of the first n primes""" diff --git a/exercises/practice/ocr-numbers/ocr_numbers_test.py b/exercises/practice/ocr-numbers/ocr_numbers_test.py index d3a11134dd..1b74ca2893 100644 --- a/exercises/practice/ocr-numbers/ocr_numbers_test.py +++ b/exercises/practice/ocr-numbers/ocr_numbers_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/ocr-numbers/canonical-data.json +# File last updated on 2023-07-15 + import unittest from ocr_numbers import ( convert, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/ocr-numbers/canonical-data.json -# File last updated on 2023-07-14 - class OcrNumbersTest(unittest.TestCase): def test_recognizes_0(self): diff --git a/exercises/practice/palindrome-products/palindrome_products_test.py b/exercises/practice/palindrome-products/palindrome_products_test.py index e748b8703c..7fc2c61bfa 100644 --- a/exercises/practice/palindrome-products/palindrome_products_test.py +++ b/exercises/practice/palindrome-products/palindrome_products_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/palindrome-products/canonical-data.json +# File last updated on 2023-07-15 + import unittest from palindrome_products import ( @@ -5,10 +9,6 @@ smallest, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/palindrome-products/canonical-data.json -# File last updated on 2023-07-14 - class PalindromeProductsTest(unittest.TestCase): def test_find_the_smallest_palindrome_from_single_digit_factors(self): diff --git a/exercises/practice/pangram/pangram_test.py b/exercises/practice/pangram/pangram_test.py index 2e5179acee..e441ac3958 100644 --- a/exercises/practice/pangram/pangram_test.py +++ b/exercises/practice/pangram/pangram_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pangram/canonical-data.json +# File last updated on 2023-07-15 + import unittest from pangram import ( is_pangram, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/pangram/canonical-data.json -# File last updated on 2023-07-14 - class PangramTest(unittest.TestCase): def test_empty_sentence(self): diff --git a/exercises/practice/pascals-triangle/pascals_triangle_test.py b/exercises/practice/pascals-triangle/pascals_triangle_test.py index a0b253af3e..e7979a15a0 100644 --- a/exercises/practice/pascals-triangle/pascals_triangle_test.py +++ b/exercises/practice/pascals-triangle/pascals_triangle_test.py @@ -1,14 +1,15 @@ import sys + +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pascals-triangle/canonical-data.json +# File last updated on 2023-07-15 + import unittest from pascals_triangle import ( rows, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/pascals-triangle/canonical-data.json -# File last updated on 2023-07-14 - TRIANGLE = [ [1], [1, 1], diff --git a/exercises/practice/perfect-numbers/perfect_numbers_test.py b/exercises/practice/perfect-numbers/perfect_numbers_test.py index 512600f12d..80479076d2 100644 --- a/exercises/practice/perfect-numbers/perfect_numbers_test.py +++ b/exercises/practice/perfect-numbers/perfect_numbers_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/perfect-numbers/canonical-data.json +# File last updated on 2023-07-15 + import unittest from perfect_numbers import ( classify, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/perfect-numbers/canonical-data.json -# File last updated on 2023-07-14 - class PerfectNumbersTest(unittest.TestCase): def test_smallest_perfect_number_is_classified_correctly(self): diff --git a/exercises/practice/phone-number/phone_number_test.py b/exercises/practice/phone-number/phone_number_test.py index 1002c6b7a1..4b887e8bbb 100644 --- a/exercises/practice/phone-number/phone_number_test.py +++ b/exercises/practice/phone-number/phone_number_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/phone-number/canonical-data.json +# File last updated on 2023-07-15 + import unittest from phone_number import ( PhoneNumber, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/phone-number/canonical-data.json -# File last updated on 2023-07-14 - class PhoneNumberTest(unittest.TestCase): def test_cleans_the_number(self): diff --git a/exercises/practice/pig-latin/pig_latin_test.py b/exercises/practice/pig-latin/pig_latin_test.py index 997f0284d5..39894d6943 100644 --- a/exercises/practice/pig-latin/pig_latin_test.py +++ b/exercises/practice/pig-latin/pig_latin_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json +# File last updated on 2023-07-15 + import unittest from pig_latin import ( translate, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json -# File last updated on 2023-07-14 - class PigLatinTest(unittest.TestCase): def test_word_beginning_with_a(self): diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py index f30462f897..1b1332dad0 100644 --- a/exercises/practice/poker/poker_test.py +++ b/exercises/practice/poker/poker_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/poker/canonical-data.json +# File last updated on 2023-07-15 + import unittest from poker import ( best_hands, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/poker/canonical-data.json -# File last updated on 2023-07-14 - class PokerTest(unittest.TestCase): def test_single_hand_always_wins(self): diff --git a/exercises/practice/pov/pov_test.py b/exercises/practice/pov/pov_test.py index 93b622d72a..c10101fa2f 100644 --- a/exercises/practice/pov/pov_test.py +++ b/exercises/practice/pov/pov_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pov/canonical-data.json +# File last updated on 2023-07-15 + import unittest from pov import ( Tree, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/pov/canonical-data.json -# File last updated on 2023-07-14 - class PovTest(unittest.TestCase): def test_results_in_the_same_tree_if_the_input_tree_is_a_singleton(self): diff --git a/exercises/practice/prime-factors/prime_factors_test.py b/exercises/practice/prime-factors/prime_factors_test.py index 187c4f2f96..ada03fe4f7 100644 --- a/exercises/practice/prime-factors/prime_factors_test.py +++ b/exercises/practice/prime-factors/prime_factors_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/prime-factors/canonical-data.json +# File last updated on 2023-07-15 + import unittest from prime_factors import ( factors, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/prime-factors/canonical-data.json -# File last updated on 2023-07-14 - class PrimeFactorsTest(unittest.TestCase): def test_no_factors(self): diff --git a/exercises/practice/protein-translation/protein_translation_test.py b/exercises/practice/protein-translation/protein_translation_test.py index 464d7f0ddd..e79fde2110 100644 --- a/exercises/practice/protein-translation/protein_translation_test.py +++ b/exercises/practice/protein-translation/protein_translation_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json +# File last updated on 2023-07-15 + import unittest from protein_translation import ( proteins, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json -# File last updated on 2023-07-14 - class ProteinTranslationTest(unittest.TestCase): def test_methionine_rna_sequence(self): diff --git a/exercises/practice/proverb/proverb_test.py b/exercises/practice/proverb/proverb_test.py index 674cc26dc0..9a86be909a 100644 --- a/exercises/practice/proverb/proverb_test.py +++ b/exercises/practice/proverb/proverb_test.py @@ -1,12 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/proverb/canonical-data.json +# File last updated on 2023-07-15 + import unittest from proverb import ( proverb, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/proverb/canonical-data.json -# File last updated on 2023-07-14 # PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.** # A new line in a result list below **does not** always equal a new list element. # Check comma placement carefully! diff --git a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py index b4b89e2d8a..663256cf8c 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py +++ b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pythagorean-triplet/canonical-data.json +# File last updated on 2023-07-15 + import unittest from pythagorean_triplet import ( triplets_with_sum, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/pythagorean-triplet/canonical-data.json -# File last updated on 2023-07-14 - class PythagoreanTripletTest(unittest.TestCase): def test_triplets_whose_sum_is_12(self): diff --git a/exercises/practice/queen-attack/queen_attack_test.py b/exercises/practice/queen-attack/queen_attack_test.py index 52f994fc39..127d506817 100644 --- a/exercises/practice/queen-attack/queen_attack_test.py +++ b/exercises/practice/queen-attack/queen_attack_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json +# File last updated on 2023-07-15 + import unittest from queen_attack import ( Queen, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json -# File last updated on 2023-07-14 - class QueenAttackTest(unittest.TestCase): # Test creation of Queens with valid and invalid positions diff --git a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py index fb546d4ab2..c2600804b7 100644 --- a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py +++ b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rail-fence-cipher/canonical-data.json +# File last updated on 2023-07-15 + import unittest from rail_fence_cipher import ( @@ -5,10 +9,6 @@ encode, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/rail-fence-cipher/canonical-data.json -# File last updated on 2023-07-14 - class RailFenceCipherTest(unittest.TestCase): def test_encode_with_two_rails(self): diff --git a/exercises/practice/raindrops/raindrops_test.py b/exercises/practice/raindrops/raindrops_test.py index a196eb32af..089b5ded67 100644 --- a/exercises/practice/raindrops/raindrops_test.py +++ b/exercises/practice/raindrops/raindrops_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/raindrops/canonical-data.json +# File last updated on 2023-07-15 + import unittest from raindrops import ( convert, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/raindrops/canonical-data.json -# File last updated on 2023-07-14 - class RaindropsTest(unittest.TestCase): def test_the_sound_for_1_is_1(self): diff --git a/exercises/practice/rational-numbers/rational_numbers_test.py b/exercises/practice/rational-numbers/rational_numbers_test.py index 3b1e44a623..031361d311 100644 --- a/exercises/practice/rational-numbers/rational_numbers_test.py +++ b/exercises/practice/rational-numbers/rational_numbers_test.py @@ -1,14 +1,15 @@ from __future__ import division + +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rational-numbers/canonical-data.json +# File last updated on 2023-07-15 + import unittest from rational_numbers import ( Rational, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/rational-numbers/canonical-data.json -# File last updated on 2023-07-14 - class RationalNumbersTest(unittest.TestCase): diff --git a/exercises/practice/react/react_test.py b/exercises/practice/react/react_test.py index 5d4f22bcc6..0a93e4d23c 100644 --- a/exercises/practice/react/react_test.py +++ b/exercises/practice/react/react_test.py @@ -1,5 +1,9 @@ from functools import partial +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/react/canonical-data.json +# File last updated on 2023-07-15 + import unittest from react import ( @@ -7,10 +11,6 @@ ComputeCell, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/react/canonical-data.json -# File last updated on 2023-07-14 - class ReactTest(unittest.TestCase): def test_input_cells_have_a_value(self): diff --git a/exercises/practice/rectangles/rectangles_test.py b/exercises/practice/rectangles/rectangles_test.py index 72cdae7cc2..19988c7bd5 100644 --- a/exercises/practice/rectangles/rectangles_test.py +++ b/exercises/practice/rectangles/rectangles_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rectangles/canonical-data.json +# File last updated on 2023-07-15 + import unittest from rectangles import ( rectangles, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/rectangles/canonical-data.json -# File last updated on 2023-07-14 - class RectanglesTest(unittest.TestCase): def test_no_rows(self): diff --git a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py index 9ace6d64b7..265135639b 100644 --- a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py +++ b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-duo/canonical-data.json +# File last updated on 2023-07-15 + import unittest from resistor_color_duo import ( value, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-duo/canonical-data.json -# File last updated on 2023-07-14 - class ResistorColorDuoTest(unittest.TestCase): def test_brown_and_black(self): diff --git a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py index 31901c5cfa..a566be48af 100644 --- a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py +++ b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-trio/canonical-data.json +# File last updated on 2023-07-15 + import unittest from resistor_color_trio import ( label, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-trio/canonical-data.json -# File last updated on 2023-07-14 - class ResistorColorTrioTest(unittest.TestCase): def test_orange_and_orange_and_black(self): diff --git a/exercises/practice/resistor-color/resistor_color_test.py b/exercises/practice/resistor-color/resistor_color_test.py index bb180385ce..c8648ea1d4 100644 --- a/exercises/practice/resistor-color/resistor_color_test.py +++ b/exercises/practice/resistor-color/resistor_color_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color/canonical-data.json +# File last updated on 2023-07-15 + import unittest from resistor_color import ( @@ -5,10 +9,6 @@ colors, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color/canonical-data.json -# File last updated on 2023-07-14 - class ResistorColorTest(unittest.TestCase): def test_black(self): diff --git a/exercises/practice/rest-api/rest_api_test.py b/exercises/practice/rest-api/rest_api_test.py index 4a00073dda..72ba438426 100644 --- a/exercises/practice/rest-api/rest_api_test.py +++ b/exercises/practice/rest-api/rest_api_test.py @@ -1,12 +1,12 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rest-api/canonical-data.json +# File last updated on 2023-07-15 + import unittest from rest_api import ( RestAPI, ) - -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/rest-api/canonical-data.json -# File last updated on 2023-07-14 import json diff --git a/exercises/practice/reverse-string/reverse_string_test.py b/exercises/practice/reverse-string/reverse_string_test.py index cedc7f890c..8fe8df5997 100644 --- a/exercises/practice/reverse-string/reverse_string_test.py +++ b/exercises/practice/reverse-string/reverse_string_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json +# File last updated on 2023-07-15 + import unittest from reverse_string import ( reverse, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json -# File last updated on 2023-07-14 - class ReverseStringTest(unittest.TestCase): def test_an_empty_string(self): diff --git a/exercises/practice/rna-transcription/rna_transcription_test.py b/exercises/practice/rna-transcription/rna_transcription_test.py index 2e58d06b93..cb6cf87054 100644 --- a/exercises/practice/rna-transcription/rna_transcription_test.py +++ b/exercises/practice/rna-transcription/rna_transcription_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rna-transcription/canonical-data.json +# File last updated on 2023-07-15 + import unittest from rna_transcription import ( to_rna, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/rna-transcription/canonical-data.json -# File last updated on 2023-07-14 - class RnaTranscriptionTest(unittest.TestCase): def test_empty_rna_sequence(self): diff --git a/exercises/practice/robot-simulator/robot_simulator_test.py b/exercises/practice/robot-simulator/robot_simulator_test.py index 2d54517681..8349b7101d 100644 --- a/exercises/practice/robot-simulator/robot_simulator_test.py +++ b/exercises/practice/robot-simulator/robot_simulator_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/robot-simulator/canonical-data.json +# File last updated on 2023-07-15 + import unittest from robot_simulator import ( @@ -8,10 +12,6 @@ WEST, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/robot-simulator/canonical-data.json -# File last updated on 2023-07-14 - class RobotSimulatorTest(unittest.TestCase): diff --git a/exercises/practice/roman-numerals/roman_numerals_test.py b/exercises/practice/roman-numerals/roman_numerals_test.py index 51ff27cb13..13b3245a21 100644 --- a/exercises/practice/roman-numerals/roman_numerals_test.py +++ b/exercises/practice/roman-numerals/roman_numerals_test.py @@ -1,12 +1,14 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json +# File last updated on 2023-07-15 + import unittest from roman_numerals import ( roman, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json -# File last updated on 2023-07-14 + class RomanNumeralsTest(unittest.TestCase): def test_1_is_i(self): self.assertEqual(roman(1), "I") diff --git a/exercises/practice/rotational-cipher/rotational_cipher_test.py b/exercises/practice/rotational-cipher/rotational_cipher_test.py index 21dbcf8ab7..802b54f9e1 100644 --- a/exercises/practice/rotational-cipher/rotational_cipher_test.py +++ b/exercises/practice/rotational-cipher/rotational_cipher_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rotational-cipher/canonical-data.json +# File last updated on 2023-07-15 + import unittest from rotational_cipher import ( rotate, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/rotational-cipher/canonical-data.json -# File last updated on 2023-07-14 - class RotationalCipherTest(unittest.TestCase): def test_rotate_a_by_0_same_output_as_input(self): diff --git a/exercises/practice/run-length-encoding/run_length_encoding_test.py b/exercises/practice/run-length-encoding/run_length_encoding_test.py index 251c5f5ca8..18f883a29e 100644 --- a/exercises/practice/run-length-encoding/run_length_encoding_test.py +++ b/exercises/practice/run-length-encoding/run_length_encoding_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json +# File last updated on 2023-07-15 + import unittest from run_length_encoding import ( @@ -5,10 +9,6 @@ decode, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json -# File last updated on 2023-07-14 - class RunLengthEncodingTest(unittest.TestCase): def test_encode_empty_string(self): diff --git a/exercises/practice/saddle-points/saddle_points_test.py b/exercises/practice/saddle-points/saddle_points_test.py index 4a33d1922b..fe966bae0f 100644 --- a/exercises/practice/saddle-points/saddle_points_test.py +++ b/exercises/practice/saddle-points/saddle_points_test.py @@ -5,16 +5,16 @@ ValueError with a meaningful error message if the matrix turns out to be irregular. """ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/saddle-points/canonical-data.json +# File last updated on 2023-07-15 + import unittest from saddle_points import ( saddle_points, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/saddle-points/canonical-data.json -# File last updated on 2023-07-14 - def sorted_points(point_list): return sorted(point_list, key=lambda p: (p["row"], p["column"])) diff --git a/exercises/practice/satellite/satellite_test.py b/exercises/practice/satellite/satellite_test.py index c1629a9ce4..60a34a7d65 100644 --- a/exercises/practice/satellite/satellite_test.py +++ b/exercises/practice/satellite/satellite_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/satellite/canonical-data.json +# File last updated on 2023-07-15 + import unittest from satellite import ( tree_from_traversals, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/satellite/canonical-data.json -# File last updated on 2023-07-14 - class SatelliteTest(unittest.TestCase): def test_empty_tree(self): diff --git a/exercises/practice/say/say_test.py b/exercises/practice/say/say_test.py index 58f9a04a9d..6d54c85fa4 100644 --- a/exercises/practice/say/say_test.py +++ b/exercises/practice/say/say_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/say/canonical-data.json +# File last updated on 2023-07-15 + import unittest from say import ( say, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/say/canonical-data.json -# File last updated on 2023-07-14 - class SayTest(unittest.TestCase): def test_zero(self): diff --git a/exercises/practice/scale-generator/scale_generator_test.py b/exercises/practice/scale-generator/scale_generator_test.py index 48f94f3d81..cec6aa34ef 100644 --- a/exercises/practice/scale-generator/scale_generator_test.py +++ b/exercises/practice/scale-generator/scale_generator_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/scale-generator/canonical-data.json +# File last updated on 2023-07-15 + import unittest from scale_generator import ( Scale, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/scale-generator/canonical-data.json -# File last updated on 2023-07-14 - class ScaleGeneratorTest(unittest.TestCase): diff --git a/exercises/practice/scrabble-score/scrabble_score_test.py b/exercises/practice/scrabble-score/scrabble_score_test.py index 1b95e627cb..5a5dca5c5c 100644 --- a/exercises/practice/scrabble-score/scrabble_score_test.py +++ b/exercises/practice/scrabble-score/scrabble_score_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/scrabble-score/canonical-data.json +# File last updated on 2023-07-15 + import unittest from scrabble_score import ( score, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/scrabble-score/canonical-data.json -# File last updated on 2023-07-14 - class ScrabbleScoreTest(unittest.TestCase): def test_lowercase_letter(self): diff --git a/exercises/practice/secret-handshake/secret_handshake_test.py b/exercises/practice/secret-handshake/secret_handshake_test.py index 6abf64d452..52f6f9116b 100644 --- a/exercises/practice/secret-handshake/secret_handshake_test.py +++ b/exercises/practice/secret-handshake/secret_handshake_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/secret-handshake/canonical-data.json +# File last updated on 2023-07-15 + import unittest from secret_handshake import ( commands, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/secret-handshake/canonical-data.json -# File last updated on 2023-07-14 - class SecretHandshakeTest(unittest.TestCase): def test_wink_for_1(self): diff --git a/exercises/practice/series/series_test.py b/exercises/practice/series/series_test.py index 9c6ee8b8c1..938ff7956d 100644 --- a/exercises/practice/series/series_test.py +++ b/exercises/practice/series/series_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/series/canonical-data.json +# File last updated on 2023-07-15 + import unittest from series import ( slices, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/series/canonical-data.json -# File last updated on 2023-07-14 - class SeriesTest(unittest.TestCase): def test_slices_of_one_from_one(self): diff --git a/exercises/practice/sgf-parsing/sgf_parsing_test.py b/exercises/practice/sgf-parsing/sgf_parsing_test.py index f7acf2b182..d157e9f920 100644 --- a/exercises/practice/sgf-parsing/sgf_parsing_test.py +++ b/exercises/practice/sgf-parsing/sgf_parsing_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/sgf-parsing/canonical-data.json +# File last updated on 2023-07-15 + import unittest from sgf_parsing import ( @@ -5,10 +9,6 @@ SgfTree, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/sgf-parsing/canonical-data.json -# File last updated on 2023-07-14 - class SgfParsingTest(unittest.TestCase): def test_empty_input(self): diff --git a/exercises/practice/sieve/sieve_test.py b/exercises/practice/sieve/sieve_test.py index 487f402db6..216e71b524 100644 --- a/exercises/practice/sieve/sieve_test.py +++ b/exercises/practice/sieve/sieve_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/sieve/canonical-data.json +# File last updated on 2023-07-15 + import unittest from sieve import ( primes, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/sieve/canonical-data.json -# File last updated on 2023-07-14 - class SieveTest(unittest.TestCase): def test_no_primes_under_two(self): diff --git a/exercises/practice/simple-cipher/simple_cipher_test.py b/exercises/practice/simple-cipher/simple_cipher_test.py index af4e2540d0..0747582c78 100644 --- a/exercises/practice/simple-cipher/simple_cipher_test.py +++ b/exercises/practice/simple-cipher/simple_cipher_test.py @@ -1,14 +1,15 @@ import re + +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/simple-cipher/canonical-data.json +# File last updated on 2023-07-15 + import unittest from simple_cipher import ( Cipher, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/simple-cipher/canonical-data.json -# File last updated on 2023-07-14 - class RandomKeyCipherTest(unittest.TestCase): def test_can_encode(self): diff --git a/exercises/practice/space-age/space_age_test.py b/exercises/practice/space-age/space_age_test.py index d1dc13b517..3b3ad50846 100644 --- a/exercises/practice/space-age/space_age_test.py +++ b/exercises/practice/space-age/space_age_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/space-age/canonical-data.json +# File last updated on 2023-07-15 + import unittest from space_age import ( SpaceAge, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/space-age/canonical-data.json -# File last updated on 2023-07-14 - class SpaceAgeTest(unittest.TestCase): def test_age_on_earth(self): diff --git a/exercises/practice/spiral-matrix/spiral_matrix_test.py b/exercises/practice/spiral-matrix/spiral_matrix_test.py index fe3d1be2b8..fadb993593 100644 --- a/exercises/practice/spiral-matrix/spiral_matrix_test.py +++ b/exercises/practice/spiral-matrix/spiral_matrix_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/spiral-matrix/canonical-data.json +# File last updated on 2023-07-15 + import unittest from spiral_matrix import ( spiral_matrix, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/spiral-matrix/canonical-data.json -# File last updated on 2023-07-14 - class SpiralMatrixTest(unittest.TestCase): def test_empty_spiral(self): diff --git a/exercises/practice/square-root/square_root_test.py b/exercises/practice/square-root/square_root_test.py index 1a8dda7ab2..8376c0dcbf 100644 --- a/exercises/practice/square-root/square_root_test.py +++ b/exercises/practice/square-root/square_root_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json +# File last updated on 2023-07-15 + import unittest from square_root import ( square_root, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json -# File last updated on 2023-07-14 - class SquareRootTest(unittest.TestCase): def test_root_of_1(self): diff --git a/exercises/practice/sublist/sublist_test.py b/exercises/practice/sublist/sublist_test.py index dcabff2121..2794aa55da 100644 --- a/exercises/practice/sublist/sublist_test.py +++ b/exercises/practice/sublist/sublist_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/sublist/canonical-data.json +# File last updated on 2023-07-15 + import unittest from sublist import ( @@ -8,10 +12,6 @@ UNEQUAL, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/sublist/canonical-data.json -# File last updated on 2023-07-14 - class SublistTest(unittest.TestCase): def test_empty_lists(self): diff --git a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py index 762840fbbc..1dc0eb943b 100644 --- a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py +++ b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/sum-of-multiples/canonical-data.json +# File last updated on 2023-07-15 + import unittest from sum_of_multiples import ( sum_of_multiples, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/sum-of-multiples/canonical-data.json -# File last updated on 2023-07-14 - class SumOfMultiplesTest(unittest.TestCase): def test_no_multiples_within_limit(self): diff --git a/exercises/practice/tournament/tournament_test.py b/exercises/practice/tournament/tournament_test.py index da8892d7df..3af02b561a 100644 --- a/exercises/practice/tournament/tournament_test.py +++ b/exercises/practice/tournament/tournament_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/tournament/canonical-data.json +# File last updated on 2023-07-15 + import unittest from tournament import ( tally, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/tournament/canonical-data.json -# File last updated on 2023-07-14 - class TournamentTest(unittest.TestCase): def test_just_the_header_if_no_input(self): diff --git a/exercises/practice/transpose/transpose_test.py b/exercises/practice/transpose/transpose_test.py index 31464c6c6e..29cbd35a2e 100644 --- a/exercises/practice/transpose/transpose_test.py +++ b/exercises/practice/transpose/transpose_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json +# File last updated on 2023-07-15 + import unittest from transpose import ( transpose, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json -# File last updated on 2023-07-14 - class TransposeTest(unittest.TestCase): def test_empty_string(self): diff --git a/exercises/practice/triangle/triangle_test.py b/exercises/practice/triangle/triangle_test.py index 7de4b34046..5ae01bc656 100644 --- a/exercises/practice/triangle/triangle_test.py +++ b/exercises/practice/triangle/triangle_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json +# File last updated on 2023-07-15 + import unittest from triangle import ( @@ -6,10 +10,6 @@ scalene, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json -# File last updated on 2023-07-14 - class EquilateralTriangleTest(unittest.TestCase): def test_all_sides_are_equal(self): diff --git a/exercises/practice/twelve-days/twelve_days_test.py b/exercises/practice/twelve-days/twelve_days_test.py index 37076cf3ef..6135922dff 100644 --- a/exercises/practice/twelve-days/twelve_days_test.py +++ b/exercises/practice/twelve-days/twelve_days_test.py @@ -1,12 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/twelve-days/canonical-data.json +# File last updated on 2023-07-15 + import unittest from twelve_days import ( recite, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/twelve-days/canonical-data.json -# File last updated on 2023-07-14 # PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.** # A new line in a result list below **does not** always equal a new list element. # Check comma placement carefully! diff --git a/exercises/practice/two-bucket/two_bucket_test.py b/exercises/practice/two-bucket/two_bucket_test.py index 7ccf413573..d8d7745310 100644 --- a/exercises/practice/two-bucket/two_bucket_test.py +++ b/exercises/practice/two-bucket/two_bucket_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/two-bucket/canonical-data.json +# File last updated on 2023-07-15 + import unittest from two_bucket import ( measure, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/two-bucket/canonical-data.json -# File last updated on 2023-07-14 - class TwoBucketTest(unittest.TestCase): def test_measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_one( diff --git a/exercises/practice/two-fer/two_fer_test.py b/exercises/practice/two-fer/two_fer_test.py index ae322272cb..31728bfe0d 100644 --- a/exercises/practice/two-fer/two_fer_test.py +++ b/exercises/practice/two-fer/two_fer_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/two-fer/canonical-data.json +# File last updated on 2023-07-15 + import unittest from two_fer import ( two_fer, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/two-fer/canonical-data.json -# File last updated on 2023-07-14 - class TwoFerTest(unittest.TestCase): def test_no_name_given(self): diff --git a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py index 5f75ec9215..45b1cdb53e 100644 --- a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py +++ b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json +# File last updated on 2023-07-15 + import unittest from variable_length_quantity import ( @@ -5,10 +9,6 @@ encode, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json -# File last updated on 2023-07-14 - class VariableLengthQuantityTest(unittest.TestCase): def test_zero(self): diff --git a/exercises/practice/word-count/word_count_test.py b/exercises/practice/word-count/word_count_test.py index 783eca5a0b..0714e8504b 100644 --- a/exercises/practice/word-count/word_count_test.py +++ b/exercises/practice/word-count/word_count_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/word-count/canonical-data.json +# File last updated on 2023-07-15 + import unittest from word_count import ( count_words, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/word-count/canonical-data.json -# File last updated on 2023-07-14 - class WordCountTest(unittest.TestCase): def test_count_one_word(self): diff --git a/exercises/practice/word-search/word_search_test.py b/exercises/practice/word-search/word_search_test.py index 2dd3d43415..a3bb890896 100644 --- a/exercises/practice/word-search/word_search_test.py +++ b/exercises/practice/word-search/word_search_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/word-search/canonical-data.json +# File last updated on 2023-07-15 + import unittest from word_search import ( @@ -5,10 +9,6 @@ Point, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/word-search/canonical-data.json -# File last updated on 2023-07-14 - class WordSearchTest(unittest.TestCase): def test_should_accept_an_initial_game_grid_and_a_target_search_word(self): diff --git a/exercises/practice/wordy/wordy_test.py b/exercises/practice/wordy/wordy_test.py index 678985ff89..eaf81c5fa4 100644 --- a/exercises/practice/wordy/wordy_test.py +++ b/exercises/practice/wordy/wordy_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json +# File last updated on 2023-07-15 + import unittest from wordy import ( answer, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json -# File last updated on 2023-07-14 - class WordyTest(unittest.TestCase): def test_just_a_number(self): diff --git a/exercises/practice/yacht/yacht_test.py b/exercises/practice/yacht/yacht_test.py index cf7d72b455..813bc8fbe6 100644 --- a/exercises/practice/yacht/yacht_test.py +++ b/exercises/practice/yacht/yacht_test.py @@ -4,7 +4,7 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/yacht/canonical-data.json -# File last updated on 2023-07-14 +# File last updated on 2023-07-15 class YachtTest(unittest.TestCase): diff --git a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py index f6ea9c82a3..7c8439e038 100644 --- a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py +++ b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/zebra-puzzle/canonical-data.json +# File last updated on 2023-07-15 + import unittest from zebra_puzzle import ( @@ -5,10 +9,6 @@ owns_zebra, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/zebra-puzzle/canonical-data.json -# File last updated on 2023-07-14 - class ZebraPuzzleTest(unittest.TestCase): def test_resident_who_drinks_water(self): diff --git a/exercises/practice/zipper/zipper_test.py b/exercises/practice/zipper/zipper_test.py index c3f3de0f6d..e7eab6c6b5 100644 --- a/exercises/practice/zipper/zipper_test.py +++ b/exercises/practice/zipper/zipper_test.py @@ -1,13 +1,13 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/zipper/canonical-data.json +# File last updated on 2023-07-15 + import unittest from zipper import ( Zipper, ) -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/zipper/canonical-data.json -# File last updated on 2023-07-14 - class ZipperTest(unittest.TestCase): def test_data_is_retained(self): From aa3e379ff131b0b7797517b8c8678066b15f1e71 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 16 Jul 2023 15:09:14 -0700 Subject: [PATCH 497/826] [DOCS]: Update Python Versions and Requirements (#3467) * Additional sweep to update Python versions and supported Python versions. * Fixed requirements and CONTRIBUTING. * Trying a different line skip to see if it fixes CI. CI is failing on test file generation again. * Once again re-rendering tests to see if it fixes CI. [no important files changed] --- CONTRIBUTING.md | 12 ++++++------ bin/generate_tests.py | 2 +- docs/ABOUT.md | 7 +++---- docs/INSTALLATION.md | 4 ++-- docs/LEARNING.md | 1 - docs/TESTS.md | 6 +++--- docs/TOOLS.md | 2 +- docs/TRACEBACKS.md | 2 -- exercises/practice/acronym/acronym_test.py | 2 +- .../practice/affine-cipher/affine_cipher_test.py | 2 +- .../practice/all-your-base/all_your_base_test.py | 2 +- exercises/practice/allergies/allergies_test.py | 2 +- exercises/practice/alphametics/alphametics_test.py | 2 +- exercises/practice/anagram/anagram_test.py | 2 +- .../armstrong-numbers/armstrong_numbers_test.py | 2 +- .../practice/atbash-cipher/atbash_cipher_test.py | 2 +- exercises/practice/bank-account/bank_account_test.py | 2 +- exercises/practice/beer-song/beer_song_test.py | 2 +- .../binary-search-tree/binary_search_tree_test.py | 2 +- .../practice/binary-search/binary_search_test.py | 2 +- exercises/practice/bob/bob_test.py | 2 +- exercises/practice/book-store/book_store_test.py | 2 +- exercises/practice/bottle-song/bottle_song_test.py | 2 +- exercises/practice/bowling/bowling_test.py | 2 +- exercises/practice/change/change_test.py | 2 +- .../practice/circular-buffer/circular_buffer_test.py | 2 +- exercises/practice/clock/clock_test.py | 2 +- .../collatz-conjecture/collatz_conjecture_test.py | 2 +- .../practice/complex-numbers/complex_numbers_test.py | 2 +- exercises/practice/connect/connect_test.py | 2 +- .../practice/crypto-square/crypto_square_test.py | 2 +- exercises/practice/custom-set/custom_set_test.py | 2 +- exercises/practice/darts/darts_test.py | 2 +- exercises/practice/diamond/diamond_test.py | 2 +- .../difference_of_squares_test.py | 2 +- .../practice/diffie-hellman/diffie_hellman_test.py | 2 +- .../practice/dnd-character/dnd_character_test.py | 2 +- exercises/practice/dominoes/dominoes_test.py | 2 +- exercises/practice/etl/etl_test.py | 2 +- .../practice/flatten-array/flatten_array_test.py | 2 +- exercises/practice/food-chain/food_chain_test.py | 2 +- exercises/practice/forth/forth_test.py | 2 +- exercises/practice/gigasecond/gigasecond_test.py | 2 +- exercises/practice/go-counting/go_counting_test.py | 2 +- exercises/practice/grade-school/grade_school_test.py | 2 +- exercises/practice/grains/grains_test.py | 2 +- exercises/practice/grep/grep_test.py | 2 +- exercises/practice/hamming/hamming_test.py | 2 +- exercises/practice/high-scores/high_scores_test.py | 2 +- exercises/practice/house/house_test.py | 2 +- .../practice/isbn-verifier/isbn_verifier_test.py | 2 +- exercises/practice/isogram/isogram_test.py | 2 +- .../killer_sudoku_helper_test.py | 2 +- .../kindergarten-garden/kindergarten_garden_test.py | 2 +- exercises/practice/knapsack/knapsack_test.py | 2 +- .../largest_series_product_test.py | 2 +- exercises/practice/leap/leap_test.py | 2 +- exercises/practice/ledger/ledger_test.py | 2 +- exercises/practice/linked-list/linked_list_test.py | 2 +- exercises/practice/list-ops/list_ops_test.py | 2 +- exercises/practice/luhn/luhn_test.py | 2 +- exercises/practice/markdown/markdown_test.py | 2 +- .../matching-brackets/matching_brackets_test.py | 2 +- exercises/practice/matrix/matrix_test.py | 2 +- exercises/practice/meetup/meetup_test.py | 2 +- exercises/practice/minesweeper/minesweeper_test.py | 2 +- exercises/practice/nth-prime/nth_prime_test.py | 2 +- exercises/practice/ocr-numbers/ocr_numbers_test.py | 2 +- .../palindrome-products/palindrome_products_test.py | 2 +- exercises/practice/pangram/pangram_test.py | 2 +- .../pascals-triangle/pascals_triangle_test.py | 2 +- .../practice/perfect-numbers/perfect_numbers_test.py | 2 +- exercises/practice/phone-number/phone_number_test.py | 2 +- exercises/practice/pig-latin/pig_latin_test.py | 2 +- exercises/practice/poker/poker_test.py | 2 +- exercises/practice/pov/pov_test.py | 2 +- .../practice/prime-factors/prime_factors_test.py | 2 +- .../protein-translation/protein_translation_test.py | 2 +- exercises/practice/proverb/proverb_test.py | 2 +- .../pythagorean-triplet/pythagorean_triplet_test.py | 2 +- exercises/practice/queen-attack/queen_attack_test.py | 2 +- .../rail-fence-cipher/rail_fence_cipher_test.py | 2 +- exercises/practice/raindrops/raindrops_test.py | 2 +- .../rational-numbers/rational_numbers_test.py | 2 +- exercises/practice/react/react_test.py | 2 +- exercises/practice/rectangles/rectangles_test.py | 2 +- .../resistor-color-duo/resistor_color_duo_test.py | 2 +- .../resistor-color-trio/resistor_color_trio_test.py | 2 +- .../practice/resistor-color/resistor_color_test.py | 2 +- exercises/practice/rest-api/rest_api_test.py | 2 +- .../practice/reverse-string/reverse_string_test.py | 2 +- .../rna-transcription/rna_transcription_test.py | 2 +- .../practice/robot-simulator/robot_simulator_test.py | 2 +- .../practice/roman-numerals/roman_numerals_test.py | 2 +- .../rotational-cipher/rotational_cipher_test.py | 2 +- .../run-length-encoding/run_length_encoding_test.py | 2 +- .../practice/saddle-points/saddle_points_test.py | 2 +- exercises/practice/satellite/satellite_test.py | 2 +- exercises/practice/say/say_test.py | 2 +- .../practice/scale-generator/scale_generator_test.py | 2 +- .../practice/scrabble-score/scrabble_score_test.py | 2 +- .../secret-handshake/secret_handshake_test.py | 2 +- exercises/practice/series/series_test.py | 2 +- exercises/practice/sgf-parsing/sgf_parsing_test.py | 2 +- exercises/practice/sieve/sieve_test.py | 2 +- .../practice/simple-cipher/simple_cipher_test.py | 2 +- exercises/practice/space-age/space_age_test.py | 2 +- .../practice/spiral-matrix/spiral_matrix_test.py | 2 +- exercises/practice/square-root/square_root_test.py | 2 +- exercises/practice/sublist/sublist_test.py | 2 +- .../sum-of-multiples/sum_of_multiples_test.py | 2 +- exercises/practice/tournament/tournament_test.py | 2 +- exercises/practice/transpose/transpose_test.py | 2 +- exercises/practice/triangle/triangle_test.py | 2 +- exercises/practice/twelve-days/twelve_days_test.py | 2 +- exercises/practice/two-bucket/two_bucket_test.py | 2 +- exercises/practice/two-fer/two_fer_test.py | 2 +- .../variable_length_quantity_test.py | 2 +- exercises/practice/word-count/word_count_test.py | 2 +- exercises/practice/word-search/word_search_test.py | 2 +- exercises/practice/wordy/wordy_test.py | 2 +- exercises/practice/yacht/.meta/template.j2 | 2 -- exercises/practice/yacht/yacht_test.py | 4 ---- exercises/practice/zebra-puzzle/zebra_puzzle_test.py | 2 +- exercises/practice/zipper/zipper_test.py | 2 +- requirements.txt | 3 +-- 126 files changed, 132 insertions(+), 143 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f082a5632..0bac23e0af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -195,12 +195,12 @@ _We know it, and trust us, we are working on fixing it._ But if you see  
-This track officially supports Python `3.7 - 3.10.6` for students completing exercises. -The track `test runner`, `analyzer`, and `representer` run in docker on `python:3.10.6-slim`. +This track officially supports Python `3.7 - 3.11.2` for students completing exercises. +The track `test runner`, `analyzer`, and `representer` run in docker on `python:3.11.2-slim`. Although the majority of test cases are written using `unittest.TestCase`, -- All exercises should be written for compatibility with Python `3.7` - `3.10.6`. +- All exercises should be written for compatibility with Python `3.7` - `3.11.2`. - Version backward _incompatibility_ (_e.g_ an exercise using features introduced in `3.8`, `3.9`, or `3.10`) should be clearly noted in any exercise hints, links, introductions or other notes. - Here is an example of how the Python documentation handles [version-tagged  🏷 ][version-tagged-language-features] feature introduction. @@ -227,7 +227,7 @@ Although the majority of test cases are written using `unittest.TestCase`, - Any updates or changes need to be proposed/approved in `problem-specifications` first. - If Python-specific changes become necessary, they need to be appended to the canonical instructions by creating a `instructions.append.md` file in this (`exercism/Python`) repository. -- Practice Exercise **Test Suits** for many practice exercises are similarly [auto-generated](#auto-generated-files) from data in [problem specifications][problem-specifications]. +- Practice Exercise **Test Suits** for most practice exercises are similarly [auto-generated](#auto-generated-files) from data in [problem specifications][problem-specifications]. - Any changes to them need to be proposed/discussed in the `problem-specifications` repository and approved by **3 track maintainers**, since changes could potentially affect many (_or all_) exercism language tracks. - If Python-specific test changes become necessary, they can be appended to the exercise `tests.toml` file. @@ -371,6 +371,7 @@ configlet generate --spec-path path/to/problem/specifications
+ [.flake8]: https://github.com/exercism/python/blob/main/.flake8 [.style.yapf]: https://github.com/exercism/python/blob/main/.style.yapf [american-english]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md @@ -380,6 +381,7 @@ configlet generate --spec-path path/to/problem/specifications [concept-exercise-anatomy]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md [concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md [config-json]: https://github.com/exercism/javascript/blob/main/config.json +[config-json]: https://github.com/exercism/python/blob/main/config.json [configlet-general]: https://github.com/exercism/configlet [configlet-lint]: https://github.com/exercism/configlet#configlet-lint [configlet]: https://github.com/exercism/docs/blob/main/building/configlet/generating-documents.md @@ -429,5 +431,3 @@ configlet generate --spec-path path/to/problem/specifications [version-tagged-language-features]: https://docs.python.org/3/library/stdtypes.html#dict.popitem [website-contributing-section]: https://exercism.org/docs/building [yapf]: https://github.com/google/yapf - -[config-json]: https://github.com/exercism/python/blob/main/config.json diff --git a/bin/generate_tests.py b/bin/generate_tests.py index 285c88d2e7..3358013705 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -271,7 +271,7 @@ def check_template(slug: str, tests_path: Path, tmpfile: Path): check_ok = False if check_ok and not filecmp.cmp(tmpfile, tests_path): with tests_path.open() as f: - for line in range(5): + for line in range(4): next(f) current_lines = f.readlines() with tmpfile.open() as f: diff --git a/docs/ABOUT.md b/docs/ABOUT.md index f0d52389bf..6177394a51 100644 --- a/docs/ABOUT.md +++ b/docs/ABOUT.md @@ -20,14 +20,14 @@ Code can be written and executed from the command line, in an interactive interp The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies and perspectives on the language. -Tests and tooling for this track currently support `3.7` - `3.10.6` (_tests_) and [`Python 3.11`][311-new-features] (_tooling_). +Tests and tooling for this track currently support `3.7` - `3.11.2` (_tests_) and [`Python 3.11.2`][311-new-features] (_tooling_). It is highly recommended that students upgrade to at least `Python 3.8`, as some features used by this track may not be supported in earlier versions. -That being said, most of the exercises will work with `Python 3.6+`. +That being said, most of the exercises will work with `Python 3.6+`, or even earlier versions. But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases]. We will try to note when a feature is only available in a certain version. -Complete documentation for the current release of Python (3.11.2) can be found at [docs.python.org][python docs]. +Complete documentation for the current release of Python (3.11.x) can be found at [docs.python.org][python docs]. - [Python Tutorial][python tutorial] - [Python Library Reference][python library reference] @@ -37,7 +37,6 @@ Complete documentation for the current release of Python (3.11.2) can be found a - [Python Glossary of Terms][python glossary of terms] - [311-new-features]: https://docs.python.org/3/whatsnew/3.11.html [active-python-releases]: https://www.python.org/downloads/ [duck typing]: https://en.wikipedia.org/wiki/Duck_typing diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index c8eb68ca46..04f9c89934 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -18,9 +18,9 @@ Some quick links into the documentation by operating system: We recommend reviewing some of the methods outlined in the Real Python article [Installing Python][installing-python] or the articles by Brett Cannon linked above. -Exercism tests and tooling currently support `3.7` - `3.10.6` (_tests_) and [`Python 3.11`][311-new-features] (_tooling_). +Exercism tests and tooling currently support `3.7` - `3.11.2` (_tests_) and [`Python 3.11.2`][311-new-features] (_tooling_). Exceptions to this support are noted where they occur. -Most of the exercises will work with `Python 3.6+`. +Most of the exercises will work with `Python 3.6+`, or even earlier versions. But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases]. diff --git a/docs/LEARNING.md b/docs/LEARNING.md index f9cb1b2205..50a3259eed 100644 --- a/docs/LEARNING.md +++ b/docs/LEARNING.md @@ -37,7 +37,6 @@ Below you will find some additional jumping-off places to start your learning jo [automate the videos]: https://www.youtube.com/watch?v=1F_OgqRuSdI&list=PL0-84-yl1fUnRuXGFe_F7qSH1LEnn9LkW [googles python class]: https://developers.google.com/edu/python/introduction [mitocw600]: https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/ -[pycharm edu]: https://www.jetbrains.com/pycharm-edu/ [python-course.eu]: https://python-course.eu/python-tutorial/ [python-for-non-programmers]: https://store.lerner.co.il/python-for-non-programmers-live [python4everyone]: https://www.py4e.com/ diff --git a/docs/TESTS.md b/docs/TESTS.md index 00ff924cd7..242555371f 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -30,14 +30,14 @@ Otherwise, the `pytest` installation will be global. ```powershell PS C:\Users\foobar> py -m pip install pytest pytest-cache pytest-subtests pytest-pylint -Successfully installed pytest-6.2.5 ... +Successfully installed pytest-7.2.2 ... ``` #### Linux / MacOS ```bash $ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint -Successfully installed pytest-6.2.5 ... +Successfully installed pytest-7.2.2 ... ``` @@ -46,7 +46,7 @@ To check if installation was successful: ```bash $ python3 -m pytest --version -pytest 6.2.5 +pytest 7.2.2 ``` ## Running the tests diff --git a/docs/TOOLS.md b/docs/TOOLS.md index f5fdedcdf1..20ce04ded0 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -30,7 +30,7 @@ If you have an editor, IDE, tool, or plugin recommendation, we encourage you to Before you start exploring, make sure that you have a recent version of Python installed. -The Exercism platform currently supports `Python 3.8` (_exercises and tests_) and `Python 3.9` (_tooling_). +The Exercism platform currently supports `Python 3.7 - 3.11.2` (_exercises and tests_) and `Python 3.11.2` (_tooling_). For more information, please refer to [Installing Python locally][Installing Python locally].
diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 4bdf92cc67..5914429db9 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -632,8 +632,6 @@ print(sum) [assert]: https://realpython.com/python-assert-statement/ [assertionerror]: https://www.geeksforgeeks.org/python-assertion-error/ -[floor divison operator]: https://www.codingem.com/python-floor-division -[logging]: https://docs.python.org/3/howto/logging.html [print]: https://www.w3schools.com/python/ref_func_print.asp [pdb]: https://www.geeksforgeeks.org/python-debugger-python-pdb/ [exception-hierarchy]: https://docs.python.org/3/library/exceptions.html#exception-hierarchy diff --git a/exercises/practice/acronym/acronym_test.py b/exercises/practice/acronym/acronym_test.py index 78557dd88b..6664ae5b20 100644 --- a/exercises/practice/acronym/acronym_test.py +++ b/exercises/practice/acronym/acronym_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/affine-cipher/affine_cipher_test.py b/exercises/practice/affine-cipher/affine_cipher_test.py index 06e6faf0da..f8e4fd59f0 100644 --- a/exercises/practice/affine-cipher/affine_cipher_test.py +++ b/exercises/practice/affine-cipher/affine_cipher_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/all-your-base/all_your_base_test.py b/exercises/practice/all-your-base/all_your_base_test.py index 9a4ca40ccd..c9f04e8dac 100644 --- a/exercises/practice/all-your-base/all_your_base_test.py +++ b/exercises/practice/all-your-base/all_your_base_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/allergies/allergies_test.py b/exercises/practice/allergies/allergies_test.py index 8d6d574e20..11f65b9b3f 100644 --- a/exercises/practice/allergies/allergies_test.py +++ b/exercises/practice/allergies/allergies_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/alphametics/alphametics_test.py b/exercises/practice/alphametics/alphametics_test.py index 014df6ceb9..911e409a3d 100644 --- a/exercises/practice/alphametics/alphametics_test.py +++ b/exercises/practice/alphametics/alphametics_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py index 69145b9d4f..fddc11b423 100644 --- a/exercises/practice/anagram/anagram_test.py +++ b/exercises/practice/anagram/anagram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py index 49831704f0..e79430483e 100644 --- a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py +++ b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/atbash-cipher/atbash_cipher_test.py b/exercises/practice/atbash-cipher/atbash_cipher_test.py index dded351c58..e6f5314cbb 100644 --- a/exercises/practice/atbash-cipher/atbash_cipher_test.py +++ b/exercises/practice/atbash-cipher/atbash_cipher_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/atbash-cipher/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/bank-account/bank_account_test.py b/exercises/practice/bank-account/bank_account_test.py index d58663395b..815442113e 100644 --- a/exercises/practice/bank-account/bank_account_test.py +++ b/exercises/practice/bank-account/bank_account_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bank-account/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/beer-song/beer_song_test.py b/exercises/practice/beer-song/beer_song_test.py index c1391989fa..8ab578a0cd 100644 --- a/exercises/practice/beer-song/beer_song_test.py +++ b/exercises/practice/beer-song/beer_song_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/beer-song/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/binary-search-tree/binary_search_tree_test.py b/exercises/practice/binary-search-tree/binary_search_tree_test.py index fdb915fe77..8a4d0ff9a2 100644 --- a/exercises/practice/binary-search-tree/binary_search_tree_test.py +++ b/exercises/practice/binary-search-tree/binary_search_tree_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search-tree/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/binary-search/binary_search_test.py b/exercises/practice/binary-search/binary_search_test.py index 6d09497734..b0bb4bac96 100644 --- a/exercises/practice/binary-search/binary_search_test.py +++ b/exercises/practice/binary-search/binary_search_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/bob/bob_test.py b/exercises/practice/bob/bob_test.py index 089636fad5..63d40c0315 100644 --- a/exercises/practice/bob/bob_test.py +++ b/exercises/practice/bob/bob_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/book-store/book_store_test.py b/exercises/practice/book-store/book_store_test.py index 66c1b72310..9d71b0cf7e 100644 --- a/exercises/practice/book-store/book_store_test.py +++ b/exercises/practice/book-store/book_store_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/book-store/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/bottle-song/bottle_song_test.py b/exercises/practice/bottle-song/bottle_song_test.py index 487a46cedc..e947b18f90 100644 --- a/exercises/practice/bottle-song/bottle_song_test.py +++ b/exercises/practice/bottle-song/bottle_song_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bottle-song/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/bowling/bowling_test.py b/exercises/practice/bowling/bowling_test.py index 374096e592..bfefd55f0a 100644 --- a/exercises/practice/bowling/bowling_test.py +++ b/exercises/practice/bowling/bowling_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/change/change_test.py b/exercises/practice/change/change_test.py index b090a3965f..7166418832 100644 --- a/exercises/practice/change/change_test.py +++ b/exercises/practice/change/change_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/circular-buffer/circular_buffer_test.py b/exercises/practice/circular-buffer/circular_buffer_test.py index b96a25e15c..077852a1d0 100644 --- a/exercises/practice/circular-buffer/circular_buffer_test.py +++ b/exercises/practice/circular-buffer/circular_buffer_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/circular-buffer/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/clock/clock_test.py b/exercises/practice/clock/clock_test.py index db4bc919cf..fa07e25b62 100644 --- a/exercises/practice/clock/clock_test.py +++ b/exercises/practice/clock/clock_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/clock/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py index d18bc37719..6c1dd79760 100644 --- a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py +++ b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/complex-numbers/complex_numbers_test.py b/exercises/practice/complex-numbers/complex_numbers_test.py index 637eb7b90d..4fd9c75ed5 100644 --- a/exercises/practice/complex-numbers/complex_numbers_test.py +++ b/exercises/practice/complex-numbers/complex_numbers_test.py @@ -2,7 +2,7 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/connect/connect_test.py b/exercises/practice/connect/connect_test.py index 239944a5cb..18c7d8f94c 100644 --- a/exercises/practice/connect/connect_test.py +++ b/exercises/practice/connect/connect_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/crypto-square/crypto_square_test.py b/exercises/practice/crypto-square/crypto_square_test.py index 08cfa87c7b..784450d366 100644 --- a/exercises/practice/crypto-square/crypto_square_test.py +++ b/exercises/practice/crypto-square/crypto_square_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index 5e6ab124be..e6d6bc120c 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/darts/darts_test.py b/exercises/practice/darts/darts_test.py index c417dabd98..ed0ed9baa6 100644 --- a/exercises/practice/darts/darts_test.py +++ b/exercises/practice/darts/darts_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/darts/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/diamond/diamond_test.py b/exercises/practice/diamond/diamond_test.py index 504380af42..cf7f1bac5e 100644 --- a/exercises/practice/diamond/diamond_test.py +++ b/exercises/practice/diamond/diamond_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/diamond/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/difference-of-squares/difference_of_squares_test.py b/exercises/practice/difference-of-squares/difference_of_squares_test.py index d75e565bef..08b3c35922 100644 --- a/exercises/practice/difference-of-squares/difference_of_squares_test.py +++ b/exercises/practice/difference-of-squares/difference_of_squares_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/difference-of-squares/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/diffie-hellman/diffie_hellman_test.py b/exercises/practice/diffie-hellman/diffie_hellman_test.py index ce5819821c..01ed7116ea 100644 --- a/exercises/practice/diffie-hellman/diffie_hellman_test.py +++ b/exercises/practice/diffie-hellman/diffie_hellman_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/diffie-hellman/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/dnd-character/dnd_character_test.py b/exercises/practice/dnd-character/dnd_character_test.py index f158f257a8..16a7959834 100644 --- a/exercises/practice/dnd-character/dnd_character_test.py +++ b/exercises/practice/dnd-character/dnd_character_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dnd-character/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/dominoes/dominoes_test.py b/exercises/practice/dominoes/dominoes_test.py index 78befc761b..3d87ec5e58 100644 --- a/exercises/practice/dominoes/dominoes_test.py +++ b/exercises/practice/dominoes/dominoes_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/etl/etl_test.py b/exercises/practice/etl/etl_test.py index d8e587bc79..8d9c628fe1 100644 --- a/exercises/practice/etl/etl_test.py +++ b/exercises/practice/etl/etl_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/etl/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/flatten-array/flatten_array_test.py b/exercises/practice/flatten-array/flatten_array_test.py index 5d73db6b6e..4c69ab1f7c 100644 --- a/exercises/practice/flatten-array/flatten_array_test.py +++ b/exercises/practice/flatten-array/flatten_array_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flatten-array/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/food-chain/food_chain_test.py b/exercises/practice/food-chain/food_chain_test.py index d330574c0b..1124f4f47c 100644 --- a/exercises/practice/food-chain/food_chain_test.py +++ b/exercises/practice/food-chain/food_chain_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/food-chain/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/forth/forth_test.py b/exercises/practice/forth/forth_test.py index 458ce47656..f4aa468d30 100644 --- a/exercises/practice/forth/forth_test.py +++ b/exercises/practice/forth/forth_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/gigasecond/gigasecond_test.py b/exercises/practice/gigasecond/gigasecond_test.py index 74017949aa..b7ebed264e 100644 --- a/exercises/practice/gigasecond/gigasecond_test.py +++ b/exercises/practice/gigasecond/gigasecond_test.py @@ -2,7 +2,7 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/gigasecond/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/go-counting/go_counting_test.py b/exercises/practice/go-counting/go_counting_test.py index 81886ca876..43eaa5b246 100644 --- a/exercises/practice/go-counting/go_counting_test.py +++ b/exercises/practice/go-counting/go_counting_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/go-counting/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/grade-school/grade_school_test.py b/exercises/practice/grade-school/grade_school_test.py index 2d2b4f5604..3f61d1303d 100644 --- a/exercises/practice/grade-school/grade_school_test.py +++ b/exercises/practice/grade-school/grade_school_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grade-school/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/grains/grains_test.py b/exercises/practice/grains/grains_test.py index e9d72f6a46..5820aa4ce1 100644 --- a/exercises/practice/grains/grains_test.py +++ b/exercises/practice/grains/grains_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/grep/grep_test.py b/exercises/practice/grep/grep_test.py index aff476d239..d15c2f3909 100644 --- a/exercises/practice/grep/grep_test.py +++ b/exercises/practice/grep/grep_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grep/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/hamming/hamming_test.py b/exercises/practice/hamming/hamming_test.py index d8d5c138a9..d9171a4449 100644 --- a/exercises/practice/hamming/hamming_test.py +++ b/exercises/practice/hamming/hamming_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/hamming/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/high-scores/high_scores_test.py b/exercises/practice/high-scores/high_scores_test.py index d1e4b71d6f..46f14477f0 100644 --- a/exercises/practice/high-scores/high_scores_test.py +++ b/exercises/practice/high-scores/high_scores_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/high-scores/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/house/house_test.py b/exercises/practice/house/house_test.py index a42b395049..a64809f83a 100644 --- a/exercises/practice/house/house_test.py +++ b/exercises/practice/house/house_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/house/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/isbn-verifier/isbn_verifier_test.py b/exercises/practice/isbn-verifier/isbn_verifier_test.py index 22fdd0a9b7..341afea5f3 100644 --- a/exercises/practice/isbn-verifier/isbn_verifier_test.py +++ b/exercises/practice/isbn-verifier/isbn_verifier_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isbn-verifier/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/isogram/isogram_test.py b/exercises/practice/isogram/isogram_test.py index b7427a7d44..09d7431d15 100644 --- a/exercises/practice/isogram/isogram_test.py +++ b/exercises/practice/isogram/isogram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isogram/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py index 24b02c08fb..023687a9bc 100644 --- a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/killer-sudoku-helper/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py index 83d4978e14..138cbb0792 100644 --- a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py +++ b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/kindergarten-garden/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/knapsack/knapsack_test.py b/exercises/practice/knapsack/knapsack_test.py index a412023784..5ce2abae03 100644 --- a/exercises/practice/knapsack/knapsack_test.py +++ b/exercises/practice/knapsack/knapsack_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/largest-series-product/largest_series_product_test.py b/exercises/practice/largest-series-product/largest_series_product_test.py index e75f022d63..3d5a90858a 100644 --- a/exercises/practice/largest-series-product/largest_series_product_test.py +++ b/exercises/practice/largest-series-product/largest_series_product_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/largest-series-product/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/leap/leap_test.py b/exercises/practice/leap/leap_test.py index d8c508de5e..69f5387962 100644 --- a/exercises/practice/leap/leap_test.py +++ b/exercises/practice/leap/leap_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/ledger/ledger_test.py b/exercises/practice/ledger/ledger_test.py index e8432bb518..d9d1027d99 100644 --- a/exercises/practice/ledger/ledger_test.py +++ b/exercises/practice/ledger/ledger_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/ledger/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 9a196084e6..c73175609a 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/linked-list/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index 4bff0080b8..0375d87564 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/list-ops/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/luhn/luhn_test.py b/exercises/practice/luhn/luhn_test.py index f6b078cca3..a83c3999d4 100644 --- a/exercises/practice/luhn/luhn_test.py +++ b/exercises/practice/luhn/luhn_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/luhn/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/markdown/markdown_test.py b/exercises/practice/markdown/markdown_test.py index 4501adb787..7d474900fc 100644 --- a/exercises/practice/markdown/markdown_test.py +++ b/exercises/practice/markdown/markdown_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/markdown/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/matching-brackets/matching_brackets_test.py b/exercises/practice/matching-brackets/matching_brackets_test.py index ae59ead1fc..e9693d23cd 100644 --- a/exercises/practice/matching-brackets/matching_brackets_test.py +++ b/exercises/practice/matching-brackets/matching_brackets_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/matching-brackets/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/matrix/matrix_test.py b/exercises/practice/matrix/matrix_test.py index 49e1ea9022..421f5bf6f7 100644 --- a/exercises/practice/matrix/matrix_test.py +++ b/exercises/practice/matrix/matrix_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/matrix/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/meetup/meetup_test.py b/exercises/practice/meetup/meetup_test.py index 24695176bd..c9d5ce0344 100644 --- a/exercises/practice/meetup/meetup_test.py +++ b/exercises/practice/meetup/meetup_test.py @@ -2,7 +2,7 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/meetup/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/minesweeper/minesweeper_test.py b/exercises/practice/minesweeper/minesweeper_test.py index 763281d66e..192cb40bc3 100644 --- a/exercises/practice/minesweeper/minesweeper_test.py +++ b/exercises/practice/minesweeper/minesweeper_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/minesweeper/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/nth-prime/nth_prime_test.py b/exercises/practice/nth-prime/nth_prime_test.py index 19c68e55f3..ce42da6db3 100644 --- a/exercises/practice/nth-prime/nth_prime_test.py +++ b/exercises/practice/nth-prime/nth_prime_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/nth-prime/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/ocr-numbers/ocr_numbers_test.py b/exercises/practice/ocr-numbers/ocr_numbers_test.py index 1b74ca2893..c7f058f103 100644 --- a/exercises/practice/ocr-numbers/ocr_numbers_test.py +++ b/exercises/practice/ocr-numbers/ocr_numbers_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/ocr-numbers/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/palindrome-products/palindrome_products_test.py b/exercises/practice/palindrome-products/palindrome_products_test.py index 7fc2c61bfa..cbe5affe20 100644 --- a/exercises/practice/palindrome-products/palindrome_products_test.py +++ b/exercises/practice/palindrome-products/palindrome_products_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/palindrome-products/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/pangram/pangram_test.py b/exercises/practice/pangram/pangram_test.py index e441ac3958..867660aaa8 100644 --- a/exercises/practice/pangram/pangram_test.py +++ b/exercises/practice/pangram/pangram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pangram/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/pascals-triangle/pascals_triangle_test.py b/exercises/practice/pascals-triangle/pascals_triangle_test.py index e7979a15a0..972ee660ff 100644 --- a/exercises/practice/pascals-triangle/pascals_triangle_test.py +++ b/exercises/practice/pascals-triangle/pascals_triangle_test.py @@ -2,7 +2,7 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pascals-triangle/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/perfect-numbers/perfect_numbers_test.py b/exercises/practice/perfect-numbers/perfect_numbers_test.py index 80479076d2..2f4f9a9bbc 100644 --- a/exercises/practice/perfect-numbers/perfect_numbers_test.py +++ b/exercises/practice/perfect-numbers/perfect_numbers_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/perfect-numbers/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/phone-number/phone_number_test.py b/exercises/practice/phone-number/phone_number_test.py index 4b887e8bbb..c7a1006ead 100644 --- a/exercises/practice/phone-number/phone_number_test.py +++ b/exercises/practice/phone-number/phone_number_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/phone-number/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/pig-latin/pig_latin_test.py b/exercises/practice/pig-latin/pig_latin_test.py index 39894d6943..c3d0a20d36 100644 --- a/exercises/practice/pig-latin/pig_latin_test.py +++ b/exercises/practice/pig-latin/pig_latin_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py index 1b1332dad0..65e64867f5 100644 --- a/exercises/practice/poker/poker_test.py +++ b/exercises/practice/poker/poker_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/poker/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/pov/pov_test.py b/exercises/practice/pov/pov_test.py index c10101fa2f..a43d104234 100644 --- a/exercises/practice/pov/pov_test.py +++ b/exercises/practice/pov/pov_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pov/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/prime-factors/prime_factors_test.py b/exercises/practice/prime-factors/prime_factors_test.py index ada03fe4f7..d0554518c8 100644 --- a/exercises/practice/prime-factors/prime_factors_test.py +++ b/exercises/practice/prime-factors/prime_factors_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/prime-factors/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/protein-translation/protein_translation_test.py b/exercises/practice/protein-translation/protein_translation_test.py index e79fde2110..cb87652d0b 100644 --- a/exercises/practice/protein-translation/protein_translation_test.py +++ b/exercises/practice/protein-translation/protein_translation_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/proverb/proverb_test.py b/exercises/practice/proverb/proverb_test.py index 9a86be909a..6e36151bf9 100644 --- a/exercises/practice/proverb/proverb_test.py +++ b/exercises/practice/proverb/proverb_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/proverb/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py index 663256cf8c..c9307e4461 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py +++ b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pythagorean-triplet/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/queen-attack/queen_attack_test.py b/exercises/practice/queen-attack/queen_attack_test.py index 127d506817..1552085624 100644 --- a/exercises/practice/queen-attack/queen_attack_test.py +++ b/exercises/practice/queen-attack/queen_attack_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py index c2600804b7..e5466ee599 100644 --- a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py +++ b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rail-fence-cipher/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/raindrops/raindrops_test.py b/exercises/practice/raindrops/raindrops_test.py index 089b5ded67..d84dd175b8 100644 --- a/exercises/practice/raindrops/raindrops_test.py +++ b/exercises/practice/raindrops/raindrops_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/raindrops/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/rational-numbers/rational_numbers_test.py b/exercises/practice/rational-numbers/rational_numbers_test.py index 031361d311..062b582d25 100644 --- a/exercises/practice/rational-numbers/rational_numbers_test.py +++ b/exercises/practice/rational-numbers/rational_numbers_test.py @@ -2,7 +2,7 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rational-numbers/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/react/react_test.py b/exercises/practice/react/react_test.py index 0a93e4d23c..ec54fa94cd 100644 --- a/exercises/practice/react/react_test.py +++ b/exercises/practice/react/react_test.py @@ -2,7 +2,7 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/react/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/rectangles/rectangles_test.py b/exercises/practice/rectangles/rectangles_test.py index 19988c7bd5..67c4d77ed4 100644 --- a/exercises/practice/rectangles/rectangles_test.py +++ b/exercises/practice/rectangles/rectangles_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rectangles/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py index 265135639b..b1bb371597 100644 --- a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py +++ b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-duo/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py index a566be48af..90f5842b23 100644 --- a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py +++ b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-trio/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/resistor-color/resistor_color_test.py b/exercises/practice/resistor-color/resistor_color_test.py index c8648ea1d4..4847708e80 100644 --- a/exercises/practice/resistor-color/resistor_color_test.py +++ b/exercises/practice/resistor-color/resistor_color_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/rest-api/rest_api_test.py b/exercises/practice/rest-api/rest_api_test.py index 72ba438426..2ef7247d40 100644 --- a/exercises/practice/rest-api/rest_api_test.py +++ b/exercises/practice/rest-api/rest_api_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rest-api/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/reverse-string/reverse_string_test.py b/exercises/practice/reverse-string/reverse_string_test.py index 8fe8df5997..79b9f6829c 100644 --- a/exercises/practice/reverse-string/reverse_string_test.py +++ b/exercises/practice/reverse-string/reverse_string_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/rna-transcription/rna_transcription_test.py b/exercises/practice/rna-transcription/rna_transcription_test.py index cb6cf87054..65bb147609 100644 --- a/exercises/practice/rna-transcription/rna_transcription_test.py +++ b/exercises/practice/rna-transcription/rna_transcription_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rna-transcription/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/robot-simulator/robot_simulator_test.py b/exercises/practice/robot-simulator/robot_simulator_test.py index 8349b7101d..f401e7c35b 100644 --- a/exercises/practice/robot-simulator/robot_simulator_test.py +++ b/exercises/practice/robot-simulator/robot_simulator_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/robot-simulator/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/roman-numerals/roman_numerals_test.py b/exercises/practice/roman-numerals/roman_numerals_test.py index 13b3245a21..2b64de76cb 100644 --- a/exercises/practice/roman-numerals/roman_numerals_test.py +++ b/exercises/practice/roman-numerals/roman_numerals_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/rotational-cipher/rotational_cipher_test.py b/exercises/practice/rotational-cipher/rotational_cipher_test.py index 802b54f9e1..db460f2d92 100644 --- a/exercises/practice/rotational-cipher/rotational_cipher_test.py +++ b/exercises/practice/rotational-cipher/rotational_cipher_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rotational-cipher/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/run-length-encoding/run_length_encoding_test.py b/exercises/practice/run-length-encoding/run_length_encoding_test.py index 18f883a29e..c3cccafb0b 100644 --- a/exercises/practice/run-length-encoding/run_length_encoding_test.py +++ b/exercises/practice/run-length-encoding/run_length_encoding_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/saddle-points/saddle_points_test.py b/exercises/practice/saddle-points/saddle_points_test.py index fe966bae0f..d972355527 100644 --- a/exercises/practice/saddle-points/saddle_points_test.py +++ b/exercises/practice/saddle-points/saddle_points_test.py @@ -7,7 +7,7 @@ """ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/saddle-points/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/satellite/satellite_test.py b/exercises/practice/satellite/satellite_test.py index 60a34a7d65..b7b23d02e3 100644 --- a/exercises/practice/satellite/satellite_test.py +++ b/exercises/practice/satellite/satellite_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/satellite/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/say/say_test.py b/exercises/practice/say/say_test.py index 6d54c85fa4..eaf7224612 100644 --- a/exercises/practice/say/say_test.py +++ b/exercises/practice/say/say_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/say/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/scale-generator/scale_generator_test.py b/exercises/practice/scale-generator/scale_generator_test.py index cec6aa34ef..7cef230494 100644 --- a/exercises/practice/scale-generator/scale_generator_test.py +++ b/exercises/practice/scale-generator/scale_generator_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/scale-generator/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/scrabble-score/scrabble_score_test.py b/exercises/practice/scrabble-score/scrabble_score_test.py index 5a5dca5c5c..aeadf63f68 100644 --- a/exercises/practice/scrabble-score/scrabble_score_test.py +++ b/exercises/practice/scrabble-score/scrabble_score_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/scrabble-score/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/secret-handshake/secret_handshake_test.py b/exercises/practice/secret-handshake/secret_handshake_test.py index 52f6f9116b..0f1e4887ea 100644 --- a/exercises/practice/secret-handshake/secret_handshake_test.py +++ b/exercises/practice/secret-handshake/secret_handshake_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/secret-handshake/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/series/series_test.py b/exercises/practice/series/series_test.py index 938ff7956d..b69e3961cd 100644 --- a/exercises/practice/series/series_test.py +++ b/exercises/practice/series/series_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/series/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/sgf-parsing/sgf_parsing_test.py b/exercises/practice/sgf-parsing/sgf_parsing_test.py index d157e9f920..8927a78972 100644 --- a/exercises/practice/sgf-parsing/sgf_parsing_test.py +++ b/exercises/practice/sgf-parsing/sgf_parsing_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sgf-parsing/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/sieve/sieve_test.py b/exercises/practice/sieve/sieve_test.py index 216e71b524..aea5ad1264 100644 --- a/exercises/practice/sieve/sieve_test.py +++ b/exercises/practice/sieve/sieve_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sieve/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/simple-cipher/simple_cipher_test.py b/exercises/practice/simple-cipher/simple_cipher_test.py index 0747582c78..b3aa2ddb99 100644 --- a/exercises/practice/simple-cipher/simple_cipher_test.py +++ b/exercises/practice/simple-cipher/simple_cipher_test.py @@ -2,7 +2,7 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/simple-cipher/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/space-age/space_age_test.py b/exercises/practice/space-age/space_age_test.py index 3b3ad50846..e5bbad29d2 100644 --- a/exercises/practice/space-age/space_age_test.py +++ b/exercises/practice/space-age/space_age_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/space-age/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/spiral-matrix/spiral_matrix_test.py b/exercises/practice/spiral-matrix/spiral_matrix_test.py index fadb993593..53015c6528 100644 --- a/exercises/practice/spiral-matrix/spiral_matrix_test.py +++ b/exercises/practice/spiral-matrix/spiral_matrix_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/spiral-matrix/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/square-root/square_root_test.py b/exercises/practice/square-root/square_root_test.py index 8376c0dcbf..1bd1d96d43 100644 --- a/exercises/practice/square-root/square_root_test.py +++ b/exercises/practice/square-root/square_root_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/sublist/sublist_test.py b/exercises/practice/sublist/sublist_test.py index 2794aa55da..bb255c77d3 100644 --- a/exercises/practice/sublist/sublist_test.py +++ b/exercises/practice/sublist/sublist_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sublist/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py index 1dc0eb943b..179fba71a5 100644 --- a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py +++ b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sum-of-multiples/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/tournament/tournament_test.py b/exercises/practice/tournament/tournament_test.py index 3af02b561a..45db6055bb 100644 --- a/exercises/practice/tournament/tournament_test.py +++ b/exercises/practice/tournament/tournament_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/tournament/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/transpose/transpose_test.py b/exercises/practice/transpose/transpose_test.py index 29cbd35a2e..4e4bee820d 100644 --- a/exercises/practice/transpose/transpose_test.py +++ b/exercises/practice/transpose/transpose_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/triangle/triangle_test.py b/exercises/practice/triangle/triangle_test.py index 5ae01bc656..5c4263cf1b 100644 --- a/exercises/practice/triangle/triangle_test.py +++ b/exercises/practice/triangle/triangle_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/twelve-days/twelve_days_test.py b/exercises/practice/twelve-days/twelve_days_test.py index 6135922dff..c48ee421ac 100644 --- a/exercises/practice/twelve-days/twelve_days_test.py +++ b/exercises/practice/twelve-days/twelve_days_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/twelve-days/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/two-bucket/two_bucket_test.py b/exercises/practice/two-bucket/two_bucket_test.py index d8d7745310..5b5841cec2 100644 --- a/exercises/practice/two-bucket/two_bucket_test.py +++ b/exercises/practice/two-bucket/two_bucket_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-bucket/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/two-fer/two_fer_test.py b/exercises/practice/two-fer/two_fer_test.py index 31728bfe0d..f9e9760b2c 100644 --- a/exercises/practice/two-fer/two_fer_test.py +++ b/exercises/practice/two-fer/two_fer_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-fer/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py index 45b1cdb53e..032403d34d 100644 --- a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py +++ b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/word-count/word_count_test.py b/exercises/practice/word-count/word_count_test.py index 0714e8504b..c620335921 100644 --- a/exercises/practice/word-count/word_count_test.py +++ b/exercises/practice/word-count/word_count_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-count/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/word-search/word_search_test.py b/exercises/practice/word-search/word_search_test.py index a3bb890896..ba1c7a7761 100644 --- a/exercises/practice/word-search/word_search_test.py +++ b/exercises/practice/word-search/word_search_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-search/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/wordy/wordy_test.py b/exercises/practice/wordy/wordy_test.py index eaf81c5fa4..7a32eba420 100644 --- a/exercises/practice/wordy/wordy_test.py +++ b/exercises/practice/wordy/wordy_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/yacht/.meta/template.j2 b/exercises/practice/yacht/.meta/template.j2 index 80eb31f7bc..a9e3ebc4bc 100644 --- a/exercises/practice/yacht/.meta/template.j2 +++ b/exercises/practice/yacht/.meta/template.j2 @@ -3,8 +3,6 @@ import unittest import {{ exercise }} -{{ macros.canonical_ref() }} - class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} def test_{{ case["description"] | to_snake }}(self): diff --git a/exercises/practice/yacht/yacht_test.py b/exercises/practice/yacht/yacht_test.py index 813bc8fbe6..58566e0607 100644 --- a/exercises/practice/yacht/yacht_test.py +++ b/exercises/practice/yacht/yacht_test.py @@ -2,10 +2,6 @@ import yacht -# These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/yacht/canonical-data.json -# File last updated on 2023-07-15 - class YachtTest(unittest.TestCase): def test_yacht(self): diff --git a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py index 7c8439e038..b64fc9069c 100644 --- a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py +++ b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/zebra-puzzle/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/exercises/practice/zipper/zipper_test.py b/exercises/practice/zipper/zipper_test.py index e7eab6c6b5..1035835835 100644 --- a/exercises/practice/zipper/zipper_test.py +++ b/exercises/practice/zipper/zipper_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/zipper/canonical-data.json -# File last updated on 2023-07-15 +# File last updated on 2023-07-16 import unittest diff --git a/requirements.txt b/requirements.txt index 5b97def747..712608f855 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ -astroid<=2.12.0 flake8~=5.0.4 pylint~=2.17.1 black<=22.3.0 yapf~=0.32.0 -tomli>=1.1.0; python_full_version < '3.11.2' \ No newline at end of file +tomli>=1.1.0; python_full_version < '3.11.2' From 6dcb6602a9a03c4fe9de8c16ed22a0f43f6fb07f Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Mon, 17 Jul 2023 00:39:29 +0200 Subject: [PATCH 498/826] Use tildas (#3468) Replaces backticks with tildas for the admonitions. --- concepts/unpacking-and-multiple-assignment/about.md | 12 ++++++------ .../introduction.md | 4 ++-- .../locomotive-engineer/.docs/instructions.md | 8 ++++---- .../locomotive-engineer/.docs/introduction.md | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index e8abaa528d..1cec2f92ec 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -17,9 +17,9 @@ This is often used in multiple assignment to group all "leftover" elements that It is common in Python to also exploit this unpacking/packing behavior when using or defining functions that take an arbitrary number of positional or keyword arguments. You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` and the "special" arguments used as `some_function(*some_tuple, **some_dict)`. -```exercism/caution +~~~~exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. -``` +~~~~ ## Multiple assignment @@ -69,9 +69,9 @@ Since `tuples` are immutable, you can't swap elements in a `tuple`. ## Unpacking -```exercism/note +~~~~exercism/note The examples below use `lists` but the same concepts apply to `tuples`. -``` +~~~~ In Python, it is possible to [unpack the elements of `list`/`tuple`/`dictionary`][unpacking] into distinct variables. Since values appear within `lists`/`tuples` in a specific order, they are unpacked into variables in the same order: @@ -305,13 +305,13 @@ c = 3 You can also write parameters before `*args` to allow for specific positional arguments. Individual keyword arguments then have to appear before `**kwargs`. -```exercism/caution +~~~~exercism/caution [Arguments have to be structured](https://www.python-engineer.com/courses/advancedpython/18-function-arguments/) like this: `def my_function(, *args, , **kwargs)` If you don't follow this order then you will get an error. -``` +~~~~ ```python >>> def my_function(a, b, *args): diff --git a/concepts/unpacking-and-multiple-assignment/introduction.md b/concepts/unpacking-and-multiple-assignment/introduction.md index a4675a771e..59cab3b4ec 100644 --- a/concepts/unpacking-and-multiple-assignment/introduction.md +++ b/concepts/unpacking-and-multiple-assignment/introduction.md @@ -17,8 +17,8 @@ This is often used in multiple assignment to group all "leftover" elements that It is common in Python to also exploit this unpacking/packing behavior when using or defining functions that take an arbitrary number of positional or keyword arguments. You will often see these "special" parameters defined as `def some_function(*args, **kwargs)` and the "special" arguments used as `some_function(*some_tuple, **some_dict)`. -```exercism/caution +~~~~exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. -``` +~~~~ [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md index af914e4924..1d915c143a 100644 --- a/exercises/concept/locomotive-engineer/.docs/instructions.md +++ b/exercises/concept/locomotive-engineer/.docs/instructions.md @@ -4,10 +4,10 @@ Your friend Linus is a Locomotive Engineer who drives cargo trains between citie Although they are amazing at handling trains, they are not amazing at handling logistics or computers. They would like to enlist your programming help organizing train details and correcting mistakes in route data. -```exercism/note +~~~~exercism/note This exercise could easily be solved using slicing, indexing, and various `dict` methods. However, we would like you to practice packing, unpacking, and multiple assignment in solving each of the tasks below. -``` +~~~~ ## 1. Create a list of all wagons @@ -75,9 +75,9 @@ The first `dict` contains the origin and destination cities the train route runs The second `dict` contains other routing details such as train speed, length, or temperature. The function should return a consolidated `dict` with all routing information. -```exercism/note +~~~~exercism/note The second `dict` can contain different/more properties than the ones shown in the example. -``` +~~~~ ```python >>> extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"}) diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index e010c07576..66d9ba1581 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -5,9 +5,9 @@ Unpacked values can then be assigned to variables within the same statement, whi The special operators `*` and `**` are often used in unpacking contexts and with multiple assignment. -```exercism/caution +~~~~exercism/caution `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. -``` +~~~~ ## Multiple assignment @@ -57,9 +57,9 @@ Since `tuples` are immutable, you can't swap elements in a `tuple`. ## Unpacking -```exercism/note +~~~~exercism/note The examples below use `lists` but the same concepts apply to `tuples`. -``` +~~~~ In Python, it is possible to [unpack the elements of `list`/`tuple`/`dictionary`][unpacking] into distinct variables. Since values appear within `lists`/`tuples` in a specific order, they are unpacked into variables in the same order: @@ -293,13 +293,13 @@ c = 3 You can also write parameters before `*args` to allow for specific positional arguments. Individual keyword arguments then have to appear before `**kwargs`. -```exercism/caution +~~~~exercism/caution [Arguments have to be structured](https://www.python-engineer.com/courses/advancedpython/18-function-arguments/) like this: `def my_function(, *args, , **kwargs)` If you don't follow this order then you will get an error. -``` +~~~~ ```python >>> def my_function(a, b, *args): From a093eaa69411ecb5afaf62a48552c622849a6a91 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 18 Jul 2023 10:33:08 +0200 Subject: [PATCH 499/826] Convert `average_run_time` to an integer. (#3464) There are two reasons for this change: 1. Having the average run time as a float gives the impression of being exact, whereas the actual run time wildly varies due to a wide variety of reasons (e.g. how busy it is on the server). That fractional component will almost never actually conform the real situation. 2. jq is often used to work with track config.json config files (e.g. to add elements to it), and it will remove any trailing .0 fractional part from a number, which caused configlet lint to fail. Those JQ scripts then have to work around this by manually adding .0 to it. --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index f386ef39d6..ec35a0a9c3 100644 --- a/config.json +++ b/config.json @@ -16,7 +16,7 @@ "highlightjs_language": "python" }, "test_runner": { - "average_run_time": 2.0 + "average_run_time": 2 }, "files": { "solution": ["%{snake_slug}.py"], From be794198e169c8e77d39194519b48f5df077735d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 21 Jul 2023 16:54:40 -0700 Subject: [PATCH 500/826] [Resistor Color Expert]: Corrected Small Typos (#3469) * Corrected small typos around resistor bands. * Due to failing CI, alterations to the test generator script were needed. The generated vs submitted diff now skips the first three lines of the file so that the generation date is not picked up and flagged as needing regeneration. Sadly, a workaround was also needed to prevent Python difflib from noting the difference anyways and producing an empty "false positive" diff. All templates and test files also needed to be altered to ensure that the first three lines of every test file will always be the autogeneration comment and date. Hopefully, this will now stop the CI failures without creating any subtle additional bugs. * Touch up to bowling template. Added back the error raising utility. * Touch up to two-bucket template to add back in error raising utility. [no important files changed] --- bin/generate_tests.py | 40 +++++++++++++------ config/complexnumbers_template.j2 | 31 ++++++++++++++ config/generator_macros.j2 | 9 ----- config/master_template.j2 | 3 +- exercises/practice/acronym/.meta/template.j2 | 5 ++- exercises/practice/acronym/acronym_test.py | 2 +- .../practice/affine-cipher/.meta/template.j2 | 5 ++- .../affine-cipher/affine_cipher_test.py | 2 +- .../practice/all-your-base/.meta/template.j2 | 5 ++- .../all-your-base/all_your_base_test.py | 2 +- .../practice/allergies/.meta/template.j2 | 2 + .../practice/allergies/allergies_test.py | 2 +- .../practice/alphametics/.meta/template.j2 | 2 + .../practice/alphametics/alphametics_test.py | 2 +- exercises/practice/anagram/.meta/template.j2 | 1 + exercises/practice/anagram/anagram_test.py | 2 +- .../armstrong-numbers/.meta/template.j2 | 2 + .../armstrong_numbers_test.py | 2 +- .../practice/atbash-cipher/.meta/template.j2 | 2 + .../atbash-cipher/atbash_cipher_test.py | 2 +- .../practice/bank-account/.meta/template.j2 | 4 +- .../bank-account/bank_account_test.py | 2 +- .../practice/beer-song/.meta/template.j2 | 2 + .../practice/beer-song/beer_song_test.py | 2 +- .../binary-search-tree/.meta/template.j2 | 7 ++-- .../binary_search_tree_test.py | 2 +- .../practice/binary-search/.meta/template.j2 | 6 ++- .../binary-search/binary_search_test.py | 2 +- exercises/practice/bob/.meta/template.j2 | 3 ++ exercises/practice/bob/bob_test.py | 2 +- .../practice/book-store/.meta/template.j2 | 5 ++- .../practice/book-store/book_store_test.py | 2 +- .../practice/bottle-song/.meta/template.j2 | 2 + .../practice/bottle-song/bottle_song_test.py | 2 +- exercises/practice/bowling/.meta/template.j2 | 8 ++-- exercises/practice/bowling/bowling_test.py | 6 +-- exercises/practice/change/.meta/template.j2 | 4 +- exercises/practice/change/change_test.py | 2 +- .../circular-buffer/.meta/template.j2 | 6 ++- .../circular-buffer/circular_buffer_test.py | 2 +- exercises/practice/clock/.meta/template.j2 | 5 ++- exercises/practice/clock/clock_test.py | 2 +- .../collatz-conjecture/.meta/template.j2 | 5 ++- .../collatz_conjecture_test.py | 2 +- .../complex-numbers/.meta/template.j2 | 5 +-- .../complex-numbers/complex_numbers_test.py | 9 +---- exercises/practice/connect/connect_test.py | 6 +-- .../practice/crypto-square/.meta/template.j2 | 2 + .../crypto-square/crypto_square_test.py | 2 +- .../practice/custom-set/.meta/template.j2 | 2 + .../practice/custom-set/custom_set_test.py | 2 +- exercises/practice/darts/.meta/template.j2 | 4 +- exercises/practice/darts/darts_test.py | 2 +- exercises/practice/diamond/.meta/template.j2 | 4 +- exercises/practice/diamond/diamond_test.py | 2 +- .../difference-of-squares/.meta/template.j2 | 4 +- .../difference_of_squares_test.py | 2 +- .../practice/diffie-hellman/.meta/template.j2 | 5 ++- .../diffie-hellman/diffie_hellman_test.py | 2 +- .../practice/dnd-character/.meta/template.j2 | 2 + .../dnd-character/dnd_character_test.py | 2 +- exercises/practice/dominoes/.meta/template.j2 | 2 + exercises/practice/dominoes/dominoes_test.py | 2 +- exercises/practice/etl/.meta/template.j2 | 5 ++- exercises/practice/etl/etl_test.py | 2 +- .../practice/flatten-array/.meta/template.j2 | 4 +- .../flatten-array/flatten_array_test.py | 2 +- .../practice/food-chain/.meta/template.j2 | 4 +- .../practice/food-chain/food_chain_test.py | 2 +- exercises/practice/forth/.meta/template.j2 | 5 ++- exercises/practice/forth/forth_test.py | 2 +- .../practice/gigasecond/.meta/template.j2 | 2 + .../practice/gigasecond/gigasecond_test.py | 5 +-- .../practice/go-counting/go_counting_test.py | 10 +---- .../practice/grade-school/.meta/template.j2 | 7 +++- .../grade-school/grade_school_test.py | 2 +- exercises/practice/grains/.meta/template.j2 | 5 ++- exercises/practice/grains/grains_test.py | 2 +- exercises/practice/grep/.meta/template.j2 | 4 +- exercises/practice/grep/grep_test.py | 4 +- exercises/practice/hamming/.meta/template.j2 | 7 +++- exercises/practice/hamming/hamming_test.py | 2 +- .../practice/hello-world/.meta/template.j2 | 1 + .../practice/hello-world/hello_world_test.py | 4 ++ .../practice/high-scores/.meta/template.j2 | 9 +++-- .../practice/high-scores/high_scores_test.py | 2 +- exercises/practice/house/.meta/template.j2 | 2 + exercises/practice/house/house_test.py | 2 +- .../practice/isbn-verifier/.meta/template.j2 | 3 +- .../isbn-verifier/isbn_verifier_test.py | 2 +- exercises/practice/isogram/.meta/template.j2 | 7 +++- exercises/practice/isogram/isogram_test.py | 2 +- .../killer-sudoku-helper/.meta/template.j2 | 5 ++- .../killer_sudoku_helper_test.py | 2 +- .../kindergarten-garden/.meta/template.j2 | 7 +++- .../kindergarten_garden_test.py | 2 +- exercises/practice/knapsack/.meta/template.j2 | 4 +- exercises/practice/knapsack/knapsack_test.py | 2 +- .../largest-series-product/.meta/template.j2 | 8 ++-- .../largest_series_product_test.py | 2 +- exercises/practice/leap/.meta/template.j2 | 4 +- exercises/practice/leap/leap_test.py | 2 +- exercises/practice/ledger/.meta/template.j2 | 2 + exercises/practice/ledger/ledger_test.py | 2 +- .../practice/linked-list/.meta/template.j2 | 5 ++- .../practice/linked-list/linked_list_test.py | 2 +- exercises/practice/list-ops/.meta/template.j2 | 5 ++- exercises/practice/list-ops/list_ops_test.py | 2 +- exercises/practice/luhn/.meta/template.j2 | 7 +++- exercises/practice/luhn/luhn_test.py | 2 +- exercises/practice/markdown/.meta/template.j2 | 4 +- exercises/practice/markdown/markdown_test.py | 2 +- .../matching-brackets/.meta/template.j2 | 4 +- .../matching_brackets_test.py | 2 +- exercises/practice/matrix/.meta/template.j2 | 11 +++-- exercises/practice/matrix/matrix_test.py | 6 +-- exercises/practice/meetup/.meta/template.j2 | 8 ++-- exercises/practice/meetup/meetup_test.py | 5 +-- .../practice/minesweeper/.meta/template.j2 | 7 +++- .../practice/minesweeper/minesweeper_test.py | 2 +- .../practice/nth-prime/.meta/template.j2 | 5 ++- .../practice/nth-prime/nth_prime_test.py | 2 +- .../practice/ocr-numbers/.meta/template.j2 | 4 +- .../practice/ocr-numbers/ocr_numbers_test.py | 2 +- .../palindrome-products/.meta/template.j2 | 5 ++- .../palindrome_products_test.py | 2 +- exercises/practice/pangram/.meta/template.j2 | 5 ++- exercises/practice/pangram/pangram_test.py | 2 +- .../pascals-triangle/.meta/template.j2 | 2 + .../pascals-triangle/pascals_triangle_test.py | 5 +-- .../perfect-numbers/.meta/template.j2 | 5 ++- .../perfect-numbers/perfect_numbers_test.py | 2 +- .../practice/phone-number/.meta/template.j2 | 3 ++ .../phone-number/phone_number_test.py | 2 +- .../practice/pig-latin/.meta/template.j2 | 4 +- .../practice/pig-latin/pig_latin_test.py | 2 +- exercises/practice/poker/.meta/template.j2 | 2 + exercises/practice/poker/poker_test.py | 2 +- exercises/practice/pov/.meta/template.j2 | 11 ++--- exercises/practice/pov/pov_test.py | 2 +- .../practice/prime-factors/.meta/template.j2 | 4 +- .../prime-factors/prime_factors_test.py | 2 +- .../protein-translation/.meta/template.j2 | 4 +- .../protein_translation_test.py | 2 +- exercises/practice/proverb/.meta/template.j2 | 3 ++ exercises/practice/proverb/proverb_test.py | 2 +- .../pythagorean-triplet/.meta/template.j2 | 3 +- .../pythagorean_triplet_test.py | 2 +- .../practice/queen-attack/.meta/template.j2 | 2 + .../queen-attack/queen_attack_test.py | 2 +- .../rail-fence-cipher/.meta/template.j2 | 4 +- .../rail_fence_cipher_test.py | 2 +- .../practice/raindrops/.meta/template.j2 | 7 +++- .../practice/raindrops/raindrops_test.py | 2 +- .../rational-numbers/.meta/template.j2 | 6 +-- .../rational-numbers/rational_numbers_test.py | 4 +- exercises/practice/react/.meta/template.j2 | 5 ++- exercises/practice/react/react_test.py | 5 +-- .../practice/rectangles/.meta/template.j2 | 4 +- .../practice/rectangles/rectangles_test.py | 2 +- .../resistor-color-duo/.meta/template.j2 | 5 ++- .../resistor_color_duo_test.py | 2 +- .../.docs/instructions.md | 9 +++-- .../resistor-color-trio/.meta/template.j2 | 5 ++- .../resistor_color_trio_test.py | 2 +- .../practice/resistor-color/.meta/template.j2 | 4 +- .../resistor-color/resistor_color_test.py | 2 +- exercises/practice/rest-api/.meta/template.j2 | 4 +- exercises/practice/rest-api/rest_api_test.py | 4 +- .../practice/reverse-string/.meta/template.j2 | 4 +- .../reverse-string/reverse_string_test.py | 2 +- .../rna-transcription/.meta/template.j2 | 2 + .../rna_transcription_test.py | 2 +- .../robot-simulator/.meta/template.j2 | 4 +- .../robot-simulator/robot_simulator_test.py | 2 +- .../practice/roman-numerals/.meta/template.j2 | 2 + .../roman-numerals/roman_numerals_test.py | 2 +- .../rotational-cipher/.meta/template.j2 | 4 +- .../rotational_cipher_test.py | 2 +- .../run-length-encoding/.meta/template.j2 | 4 +- .../run_length_encoding_test.py | 2 +- .../practice/saddle-points/.meta/template.j2 | 11 ++--- .../saddle-points/saddle_points_test.py | 9 +---- .../practice/satellite/.meta/template.j2 | 4 +- .../practice/satellite/satellite_test.py | 2 +- exercises/practice/say/.meta/template.j2 | 6 ++- exercises/practice/say/say_test.py | 2 +- .../scale-generator/.meta/template.j2 | 4 +- .../scale-generator/scale_generator_test.py | 2 +- .../practice/scrabble-score/.meta/template.j2 | 5 ++- .../scrabble-score/scrabble_score_test.py | 2 +- .../secret-handshake/.meta/template.j2 | 4 +- .../secret-handshake/secret_handshake_test.py | 2 +- exercises/practice/series/.meta/template.j2 | 4 +- exercises/practice/series/series_test.py | 2 +- .../practice/sgf-parsing/.meta/template.j2 | 4 +- .../practice/sgf-parsing/sgf_parsing_test.py | 2 +- exercises/practice/sieve/.meta/template.j2 | 4 +- exercises/practice/sieve/sieve_test.py | 2 +- .../practice/simple-cipher/.meta/template.j2 | 3 ++ .../simple-cipher/simple_cipher_test.py | 5 +-- .../practice/space-age/.meta/template.j2 | 2 + .../practice/space-age/space_age_test.py | 2 +- .../practice/spiral-matrix/.meta/template.j2 | 4 +- .../spiral-matrix/spiral_matrix_test.py | 2 +- .../practice/square-root/.meta/template.j2 | 5 ++- .../practice/square-root/square_root_test.py | 2 +- exercises/practice/sublist/.meta/template.j2 | 5 ++- exercises/practice/sublist/sublist_test.py | 2 +- .../sum-of-multiples/.meta/template.j2 | 2 + .../sum-of-multiples/sum_of_multiples_test.py | 2 +- .../practice/tournament/.meta/template.j2 | 4 +- .../practice/tournament/tournament_test.py | 2 +- .../practice/transpose/.meta/template.j2 | 4 +- .../practice/transpose/transpose_test.py | 2 +- exercises/practice/triangle/.meta/template.j2 | 2 + exercises/practice/triangle/triangle_test.py | 2 +- .../practice/twelve-days/.meta/template.j2 | 5 ++- .../practice/twelve-days/twelve_days_test.py | 2 +- .../practice/two-bucket/.meta/template.j2 | 9 +++-- .../practice/two-bucket/two_bucket_test.py | 6 +-- exercises/practice/two-fer/.meta/template.j2 | 4 +- exercises/practice/two-fer/two_fer_test.py | 2 +- .../.meta/template.j2 | 6 ++- .../variable_length_quantity_test.py | 2 +- .../practice/word-count/.meta/template.j2 | 5 ++- .../practice/word-count/word_count_test.py | 2 +- .../practice/word-search/.meta/template.j2 | 2 + .../practice/word-search/word_search_test.py | 2 +- exercises/practice/wordy/.meta/template.j2 | 5 ++- exercises/practice/wordy/wordy_test.py | 2 +- exercises/practice/yacht/.meta/template.j2 | 3 +- exercises/practice/yacht/yacht_test.py | 5 ++- .../practice/zebra-puzzle/.meta/template.j2 | 4 +- .../zebra-puzzle/zebra_puzzle_test.py | 2 +- exercises/practice/zipper/.meta/template.j2 | 8 ++-- exercises/practice/zipper/zipper_test.py | 2 +- 237 files changed, 571 insertions(+), 330 deletions(-) create mode 100644 config/complexnumbers_template.j2 diff --git a/bin/generate_tests.py b/bin/generate_tests.py index 3358013705..6c9b1c38b9 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -261,6 +261,19 @@ def format_file(path: Path) -> NoReturn: def check_template(slug: str, tests_path: Path, tmpfile: Path): + """Generate a new test file and diff against existing file. + + Note: The timestamp in each test file creates issues with + Python difflib, so it is skipped when being prepped + for diff. + + You can see this "skipping" on lines 281 & 283. + However, this rather crude method creates + an empty "false positive" diff. This empty diff is + then skipped in lines 293 & 294, so that it can be + considered a pass.. + """ + try: check_ok = True if not tmpfile.is_file(): @@ -271,24 +284,25 @@ def check_template(slug: str, tests_path: Path, tmpfile: Path): check_ok = False if check_ok and not filecmp.cmp(tmpfile, tests_path): with tests_path.open() as f: - for line in range(4): - next(f) - current_lines = f.readlines() + current_lines = f.readlines()[3:] with tmpfile.open() as f: - for line in range(4): - next(f) - rendered_lines = f.readlines() - diff = difflib.unified_diff( + rendered_lines = f.readlines()[3:] + + diff = list(difflib.unified_diff( current_lines, rendered_lines, fromfile=f"[current] {tests_path.name}", tofile=f"[generated] {tmpfile.name}", - ) - logger.debug(f"{slug}: ##### DIFF START #####") - for line in diff: - logger.debug(line.strip()) - logger.debug(f"{slug}: ##### DIFF END #####") - check_ok = False + lineterm="\n", + )) + if not diff: + check_ok = True + else: + logger.debug(f"{slug}: ##### DIFF START #####") + for line in diff: + logger.debug(line.strip()) + logger.debug(f"{slug}: ##### DIFF END #####") + check_ok = False if not check_ok: logger.error( f"{slug}: check failed; tests must be regenerated with bin/generate_tests.py" diff --git a/config/complexnumbers_template.j2 b/config/complexnumbers_template.j2 new file mode 100644 index 0000000000..d70c866f1c --- /dev/null +++ b/config/complexnumbers_template.j2 @@ -0,0 +1,31 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +import math +{{ macros.header(imports=imports, ignore=ignore) }} + +{%- macro test_cases_recursive(cases) -%} +{% for case in cases -%} +{% if "cases" in case %} + # {{ case["description"] }} + {{ test_cases_recursive(case["cases"]) }} +{% else %} + {{ test_case(case) }} +{% endif -%} +{% endfor -%} +{% endmacro %} + +{% if not additional_tests -%} +{%- macro additional_tests() -%} + {{ test_cases_recursive(additional_cases) }} +{% endmacro %} +{%- endif %} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {{ test_cases_recursive(cases) }} + + {% if additional_cases | length -%} + # Additional tests for this track + {{ additional_tests() }} + {%- endif %} + diff --git a/config/generator_macros.j2 b/config/generator_macros.j2 index 37d8e45a80..b192755292 100644 --- a/config/generator_macros.j2 +++ b/config/generator_macros.j2 @@ -16,7 +16,6 @@ {%- endmacro %} {% macro header(imports=[], ignore=[]) -%} -{{ canonical_ref() }} import unittest @@ -38,14 +37,6 @@ from {{ exercise | to_snake }} import ({% if imports -%} return self.assertRaisesRegex(exception, r".+") {%- endmacro %} -{% macro footer(_has_error_case) -%} -{% if has_error_case or _has_error_case %} -{{ utility() }} -{% endif %} -if __name__ == '__main__': - unittest.main() -{%- endmacro %} - {% macro empty_set(set, list, class_name) -%} {%- if list|length > 0 -%} {{ set }} = {{ class_name }}({{ list }}) diff --git a/config/master_template.j2 b/config/master_template.j2 index 4fe2b9798b..774771fd5d 100644 --- a/config/master_template.j2 +++ b/config/master_template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(imports=imports, ignore=ignore) }} {%- macro test_cases_recursive(cases) -%} @@ -25,4 +27,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): # Additional tests for this track {{ additional_tests() }} {%- endif %} -{{ macros.footer() }} diff --git a/exercises/practice/acronym/.meta/template.j2 b/exercises/practice/acronym/.meta/template.j2 index c2e35812b1..3480ade631 100644 --- a/exercises/practice/acronym/.meta/template.j2 +++ b/exercises/practice/acronym/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} {% macro test_case(case) -%} {%- set input = case["input"] -%} @@ -8,7 +11,7 @@ "{{ case["expected"] }}" ) {%- endmacro %} -{{ macros.header()}} + class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/acronym/acronym_test.py b/exercises/practice/acronym/acronym_test.py index 6664ae5b20..984deef60d 100644 --- a/exercises/practice/acronym/acronym_test.py +++ b/exercises/practice/acronym/acronym_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/affine-cipher/.meta/template.j2 b/exercises/practice/affine-cipher/.meta/template.j2 index 2ccc2c7f86..2e92e2b4e5 100644 --- a/exercises/practice/affine-cipher/.meta/template.j2 +++ b/exercises/practice/affine-cipher/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} {% macro test_supercase(supercase) %} {% for case in supercase["cases"] -%} @@ -25,8 +28,6 @@ def test_{{ case["description"] | to_snake }}(self): {% endif -%} {%- endmacro %} -{{ macros.header() }} - class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases -%} {{ test_supercase(supercase) }} diff --git a/exercises/practice/affine-cipher/affine_cipher_test.py b/exercises/practice/affine-cipher/affine_cipher_test.py index f8e4fd59f0..f6d7c106c3 100644 --- a/exercises/practice/affine-cipher/affine_cipher_test.py +++ b/exercises/practice/affine-cipher/affine_cipher_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/all-your-base/.meta/template.j2 b/exercises/practice/all-your-base/.meta/template.j2 index b19efb5b8b..682f9d9e99 100644 --- a/exercises/practice/all-your-base/.meta/template.j2 +++ b/exercises/practice/all-your-base/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} {%- macro func_call(case) -%} {{ case["property"] }}( @@ -22,8 +25,6 @@ {%- endif %} {% endmacro %} -{{ macros.header() }} - class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} {{ test_case(case) }} diff --git a/exercises/practice/all-your-base/all_your_base_test.py b/exercises/practice/all-your-base/all_your_base_test.py index c9f04e8dac..32c0b7b924 100644 --- a/exercises/practice/all-your-base/all_your_base_test.py +++ b/exercises/practice/all-your-base/all_your_base_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/allergies/.meta/template.j2 b/exercises/practice/allergies/.meta/template.j2 index a15dc17639..d0085deb8e 100644 --- a/exercises/practice/allergies/.meta/template.j2 +++ b/exercises/practice/allergies/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(imports=[ exercise | camel_case ]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/allergies/allergies_test.py b/exercises/practice/allergies/allergies_test.py index 11f65b9b3f..8e10b9d59b 100644 --- a/exercises/practice/allergies/allergies_test.py +++ b/exercises/practice/allergies/allergies_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/alphametics/.meta/template.j2 b/exercises/practice/alphametics/.meta/template.j2 index 73291b8a1e..e051fd81ac 100644 --- a/exercises/practice/alphametics/.meta/template.j2 +++ b/exercises/practice/alphametics/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/alphametics/alphametics_test.py b/exercises/practice/alphametics/alphametics_test.py index 911e409a3d..6279b805c5 100644 --- a/exercises/practice/alphametics/alphametics_test.py +++ b/exercises/practice/alphametics/alphametics_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/anagram/.meta/template.j2 b/exercises/practice/anagram/.meta/template.j2 index 1f74aef5bc..c402f530b4 100644 --- a/exercises/practice/anagram/.meta/template.j2 +++ b/exercises/practice/anagram/.meta/template.j2 @@ -1,4 +1,5 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} {{ macros.header() }} diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py index fddc11b423..5b0e9c6c4d 100644 --- a/exercises/practice/anagram/anagram_test.py +++ b/exercises/practice/anagram/anagram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/armstrong-numbers/.meta/template.j2 b/exercises/practice/armstrong-numbers/.meta/template.j2 index d9a1cceb6d..8f917dfffc 100644 --- a/exercises/practice/armstrong-numbers/.meta/template.j2 +++ b/exercises/practice/armstrong-numbers/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py index e79430483e..402476671d 100644 --- a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py +++ b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/atbash-cipher/.meta/template.j2 b/exercises/practice/atbash-cipher/.meta/template.j2 index 1ad9d5759c..39908eb4bc 100644 --- a/exercises/practice/atbash-cipher/.meta/template.j2 +++ b/exercises/practice/atbash-cipher/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/atbash-cipher/atbash_cipher_test.py b/exercises/practice/atbash-cipher/atbash_cipher_test.py index e6f5314cbb..f8103f81b9 100644 --- a/exercises/practice/atbash-cipher/atbash_cipher_test.py +++ b/exercises/practice/atbash-cipher/atbash_cipher_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/atbash-cipher/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/bank-account/.meta/template.j2 b/exercises/practice/bank-account/.meta/template.j2 index f077837a0f..3372b2f7fd 100644 --- a/exercises/practice/bank-account/.meta/template.j2 +++ b/exercises/practice/bank-account/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["BankAccount"]) }} {% macro test_case(case) -%} def test_{{ case["description"] | to_snake }}(self): account = BankAccount() @@ -36,7 +39,6 @@ {%- endif %} {% endmacro %} -{{ macros.header(["BankAccount"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/bank-account/bank_account_test.py b/exercises/practice/bank-account/bank_account_test.py index 815442113e..c6e6b87020 100644 --- a/exercises/practice/bank-account/bank_account_test.py +++ b/exercises/practice/bank-account/bank_account_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bank-account/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/beer-song/.meta/template.j2 b/exercises/practice/beer-song/.meta/template.j2 index 7fd4a26de7..1a2d602287 100644 --- a/exercises/practice/beer-song/.meta/template.j2 +++ b/exercises/practice/beer-song/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/beer-song/beer_song_test.py b/exercises/practice/beer-song/beer_song_test.py index 8ab578a0cd..9232ca1c9b 100644 --- a/exercises/practice/beer-song/beer_song_test.py +++ b/exercises/practice/beer-song/beer_song_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/beer-song/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/binary-search-tree/.meta/template.j2 b/exercises/practice/binary-search-tree/.meta/template.j2 index e2606ddfcc..37177a163c 100644 --- a/exercises/practice/binary-search-tree/.meta/template.j2 +++ b/exercises/practice/binary-search-tree/.meta/template.j2 @@ -1,6 +1,9 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} -{%- macro build_tree(tree_obj) -%} +{{ macros.header (imports=["BinarySearchTree", "TreeNode"]) }} + +{% macro build_tree(tree_obj) %} {%- if tree_obj is none -%} None {%- else -%} @@ -25,8 +28,6 @@ TreeNode("{{ tree_obj["data"] }}", self.{{ assertion }}(BinarySearchTree({{ tree_data }}).{{ prop | to_snake }}(),expected) {%- endmacro -%} -{{ macros.header (imports=["BinarySearchTree", "TreeNode"]) }} - class {{ exercise | camel_case }}Test(unittest.TestCase): {%- for case in cases %} {%- if "cases" in case %} diff --git a/exercises/practice/binary-search-tree/binary_search_tree_test.py b/exercises/practice/binary-search-tree/binary_search_tree_test.py index 8a4d0ff9a2..f44fe2d5f9 100644 --- a/exercises/practice/binary-search-tree/binary_search_tree_test.py +++ b/exercises/practice/binary-search-tree/binary_search_tree_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search-tree/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/binary-search/.meta/template.j2 b/exercises/practice/binary-search/.meta/template.j2 index f29df25319..0856646f6a 100644 --- a/exercises/practice/binary-search/.meta/template.j2 +++ b/exercises/practice/binary-search/.meta/template.j2 @@ -1,13 +1,15 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header() }} {%- macro test_call(case) %} {{ case["property"] }}( {{ case["input"]["array"] }}, {{ case["input"]["value"] }} ) -{% endmacro -%} +{% endmacro %} -{{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/binary-search/binary_search_test.py b/exercises/practice/binary-search/binary_search_test.py index b0bb4bac96..a47a87f965 100644 --- a/exercises/practice/binary-search/binary_search_test.py +++ b/exercises/practice/binary-search/binary_search_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/bob/.meta/template.j2 b/exercises/practice/bob/.meta/template.j2 index 07df6e8eff..912f7e31bb 100644 --- a/exercises/practice/bob/.meta/template.j2 +++ b/exercises/practice/bob/.meta/template.j2 @@ -1,6 +1,9 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header() }} + class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} def test_{{ case["description"] | to_snake }}(self): diff --git a/exercises/practice/bob/bob_test.py b/exercises/practice/bob/bob_test.py index 63d40c0315..faba5f9612 100644 --- a/exercises/practice/bob/bob_test.py +++ b/exercises/practice/bob/bob_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/book-store/.meta/template.j2 b/exercises/practice/book-store/.meta/template.j2 index 735ecb6487..9e17ab02ee 100644 --- a/exercises/practice/book-store/.meta/template.j2 +++ b/exercises/practice/book-store/.meta/template.j2 @@ -1,11 +1,14 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header() }} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): basket = {{ input["basket"] }} self.assertEqual({{ case["property"] }}(basket), {{ case["expected"] }}) {%- endmacro %} -{{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/book-store/book_store_test.py b/exercises/practice/book-store/book_store_test.py index 9d71b0cf7e..87b0051faa 100644 --- a/exercises/practice/book-store/book_store_test.py +++ b/exercises/practice/book-store/book_store_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/book-store/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/bottle-song/.meta/template.j2 b/exercises/practice/bottle-song/.meta/template.j2 index 7fd4a26de7..1a2d602287 100644 --- a/exercises/practice/bottle-song/.meta/template.j2 +++ b/exercises/practice/bottle-song/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/bottle-song/bottle_song_test.py b/exercises/practice/bottle-song/bottle_song_test.py index e947b18f90..998721d4bf 100644 --- a/exercises/practice/bottle-song/bottle_song_test.py +++ b/exercises/practice/bottle-song/bottle_song_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bottle-song/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/bowling/.meta/template.j2 b/exercises/practice/bowling/.meta/template.j2 index 8234dd1c5a..2761a8de84 100644 --- a/exercises/practice/bowling/.meta/template.j2 +++ b/exercises/practice/bowling/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["BowlingGame"]) }} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -16,7 +20,6 @@ self.assertEqual(game.score(), {{ case["expected"] }}) {% endif %} {%- endmacro %} -{{ macros.header(["BowlingGame"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): def roll_new_game(self, rolls): @@ -29,5 +32,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {{ test_case(case) }} {% endfor %} - -{{ macros.footer() }} +{{ macros.utility() }} diff --git a/exercises/practice/bowling/bowling_test.py b/exercises/practice/bowling/bowling_test.py index bfefd55f0a..45d6b87483 100644 --- a/exercises/practice/bowling/bowling_test.py +++ b/exercises/practice/bowling/bowling_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-21 import unittest @@ -211,7 +211,3 @@ def test_cannot_roll_after_bonus_rolls_for_strike(self): # Utility functions def assertRaisesWithMessage(self, exception): return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/change/.meta/template.j2 b/exercises/practice/change/.meta/template.j2 index 72e4382dcb..846a965285 100644 --- a/exercises/practice/change/.meta/template.j2 +++ b/exercises/practice/change/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/change/change_test.py b/exercises/practice/change/change_test.py index 7166418832..f8699e2f6d 100644 --- a/exercises/practice/change/change_test.py +++ b/exercises/practice/change/change_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/circular-buffer/.meta/template.j2 b/exercises/practice/circular-buffer/.meta/template.j2 index 259d953949..a7aea897f6 100644 --- a/exercises/practice/circular-buffer/.meta/template.j2 +++ b/exercises/practice/circular-buffer/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["CircularBuffer","BufferEmptyException", "BufferFullException"]) }} + {% macro call_op(op) -%} buf.{{ op["operation"] }}( {% if "item" in op -%} @@ -6,7 +10,7 @@ buf.{{ op["operation"] }}( {%- endif -%} ) {%- endmacro -%} -{{ macros.header(["CircularBuffer","BufferEmptyException", "BufferFullException"]) }} + class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/circular-buffer/circular_buffer_test.py b/exercises/practice/circular-buffer/circular_buffer_test.py index 077852a1d0..eb0663cf50 100644 --- a/exercises/practice/circular-buffer/circular_buffer_test.py +++ b/exercises/practice/circular-buffer/circular_buffer_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/circular-buffer/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/clock/.meta/template.j2 b/exercises/practice/clock/.meta/template.j2 index c8cee7e08d..872dce0a42 100644 --- a/exercises/practice/clock/.meta/template.j2 +++ b/exercises/practice/clock/.meta/template.j2 @@ -1,8 +1,11 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["Clock"])}} + {%- macro clock(obj) -%} Clock({{ obj["hour"] }}, {{ obj["minute"] }}) {%- endmacro -%} -{{ macros.header(["Clock"])}} {% set cases = additional_cases + cases %} diff --git a/exercises/practice/clock/clock_test.py b/exercises/practice/clock/clock_test.py index fa07e25b62..6fde8e83e7 100644 --- a/exercises/practice/clock/clock_test.py +++ b/exercises/practice/clock/clock_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/clock/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/collatz-conjecture/.meta/template.j2 b/exercises/practice/collatz-conjecture/.meta/template.j2 index 4dcbae8550..988f4a4ed9 100644 --- a/exercises/practice/collatz-conjecture/.meta/template.j2 +++ b/exercises/practice/collatz-conjecture/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} def test_{{ case["description"] | to_snake }}(self): {% set expected = case["expected"] -%} @@ -15,7 +19,6 @@ ) {% endif %} {%- endmacro %} -{{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py index 6c1dd79760..306e3db7e7 100644 --- a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py +++ b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 import unittest diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index 86b0d71d84..7c3fb7d2dd 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -1,7 +1,6 @@ -import math +{% extends "complexnumbers_template.j2" %} -{% extends "master_template.j2" -%} -{%- set imports = ["ComplexNumber"] -%} +{% set imports = ["ComplexNumber"] %} {%- macro translate_math(item) -%} {{ item | replace("pi", "math.pi") | replace("e", "math.e") | replace("ln", "math.log") }} diff --git a/exercises/practice/complex-numbers/complex_numbers_test.py b/exercises/practice/complex-numbers/complex_numbers_test.py index 4fd9c75ed5..3ebdcfe108 100644 --- a/exercises/practice/complex-numbers/complex_numbers_test.py +++ b/exercises/practice/complex-numbers/complex_numbers_test.py @@ -1,9 +1,8 @@ -import math - # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 +import math import unittest from complex_numbers import ( @@ -191,7 +190,3 @@ def test_inequality_of_real_part(self): def test_inequality_of_imaginary_part(self): self.assertNotEqual(ComplexNumber(1, 2), ComplexNumber(1, 1)) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/connect/connect_test.py b/exercises/practice/connect/connect_test.py index 18c7d8f94c..e7303d3513 100644 --- a/exercises/practice/connect/connect_test.py +++ b/exercises/practice/connect/connect_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest @@ -110,7 +110,3 @@ def test_x_wins_using_a_spiral_path(self): ) winner = game.get_winner() self.assertEqual(winner, "X") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/crypto-square/.meta/template.j2 b/exercises/practice/crypto-square/.meta/template.j2 index c33812e64c..f968f0c747 100644 --- a/exercises/practice/crypto-square/.meta/template.j2 +++ b/exercises/practice/crypto-square/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(['cipher_text']) }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/crypto-square/crypto_square_test.py b/exercises/practice/crypto-square/crypto_square_test.py index 784450d366..97630a6750 100644 --- a/exercises/practice/crypto-square/crypto_square_test.py +++ b/exercises/practice/crypto-square/crypto_square_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/custom-set/.meta/template.j2 b/exercises/practice/custom-set/.meta/template.j2 index d70a45c298..e7a4ee7d37 100644 --- a/exercises/practice/custom-set/.meta/template.j2 +++ b/exercises/practice/custom-set/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {% set class_name = exercise | camel_case -%} {{ macros.header([class_name]) }} diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index e6d6bc120c..9ee2bd1967 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/darts/.meta/template.j2 b/exercises/practice/darts/.meta/template.j2 index 35a5c37dd3..e08c6d83c5 100644 --- a/exercises/practice/darts/.meta/template.j2 +++ b/exercises/practice/darts/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/darts/darts_test.py b/exercises/practice/darts/darts_test.py index ed0ed9baa6..5e16704617 100644 --- a/exercises/practice/darts/darts_test.py +++ b/exercises/practice/darts/darts_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/darts/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/diamond/.meta/template.j2 b/exercises/practice/diamond/.meta/template.j2 index 5651fa6bc9..0175388cc3 100644 --- a/exercises/practice/diamond/.meta/template.j2 +++ b/exercises/practice/diamond/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/diamond/diamond_test.py b/exercises/practice/diamond/diamond_test.py index cf7f1bac5e..6a3a229509 100644 --- a/exercises/practice/diamond/diamond_test.py +++ b/exercises/practice/diamond/diamond_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/diamond/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/difference-of-squares/.meta/template.j2 b/exercises/practice/difference-of-squares/.meta/template.j2 index ffac71e284..db8cc69e9c 100644 --- a/exercises/practice/difference-of-squares/.meta/template.j2 +++ b/exercises/practice/difference-of-squares/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases %} diff --git a/exercises/practice/difference-of-squares/difference_of_squares_test.py b/exercises/practice/difference-of-squares/difference_of_squares_test.py index 08b3c35922..aa7271907a 100644 --- a/exercises/practice/difference-of-squares/difference_of_squares_test.py +++ b/exercises/practice/difference-of-squares/difference_of_squares_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/difference-of-squares/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/diffie-hellman/.meta/template.j2 b/exercises/practice/diffie-hellman/.meta/template.j2 index b9634cdc30..378c28423d 100644 --- a/exercises/practice/diffie-hellman/.meta/template.j2 +++ b/exercises/practice/diffie-hellman/.meta/template.j2 @@ -1,7 +1,10 @@ {%- import "generator_macros.j2" as macros with context -%} -{% set class = exercise | camel_case -%} +{{ macros.canonical_ref() }} + {{ macros.header(["private_key", "public_key", "secret"]) }} +{% set class = exercise | camel_case -%} + class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} {% set property = case["property"] | to_snake -%} diff --git a/exercises/practice/diffie-hellman/diffie_hellman_test.py b/exercises/practice/diffie-hellman/diffie_hellman_test.py index 01ed7116ea..e24c4e742a 100644 --- a/exercises/practice/diffie-hellman/diffie_hellman_test.py +++ b/exercises/practice/diffie-hellman/diffie_hellman_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/diffie-hellman/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/dnd-character/.meta/template.j2 b/exercises/practice/dnd-character/.meta/template.j2 index adb1565053..793f3d921d 100644 --- a/exercises/practice/dnd-character/.meta/template.j2 +++ b/exercises/practice/dnd-character/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(["Character", "modifier"]) }} {% macro test_case(supercase) -%} diff --git a/exercises/practice/dnd-character/dnd_character_test.py b/exercises/practice/dnd-character/dnd_character_test.py index 16a7959834..e506fdb117 100644 --- a/exercises/practice/dnd-character/dnd_character_test.py +++ b/exercises/practice/dnd-character/dnd_character_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dnd-character/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/dominoes/.meta/template.j2 b/exercises/practice/dominoes/.meta/template.j2 index 3ec60875d0..f1f226b3c5 100644 --- a/exercises/practice/dominoes/.meta/template.j2 +++ b/exercises/practice/dominoes/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header()}} {% macro tuplify(dominoes_list) -%} diff --git a/exercises/practice/dominoes/dominoes_test.py b/exercises/practice/dominoes/dominoes_test.py index 3d87ec5e58..4407036af1 100644 --- a/exercises/practice/dominoes/dominoes_test.py +++ b/exercises/practice/dominoes/dominoes_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/etl/.meta/template.j2 b/exercises/practice/etl/.meta/template.j2 index 5669b6ed99..0fc3ccb39f 100644 --- a/exercises/practice/etl/.meta/template.j2 +++ b/exercises/practice/etl/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -9,7 +13,6 @@ {{ case["property"] | to_snake }}(legacy_data), data ) {%- endmacro %} -{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/etl/etl_test.py b/exercises/practice/etl/etl_test.py index 8d9c628fe1..d6eed70a57 100644 --- a/exercises/practice/etl/etl_test.py +++ b/exercises/practice/etl/etl_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/etl/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/flatten-array/.meta/template.j2 b/exercises/practice/flatten-array/.meta/template.j2 index 425e92fee9..0a9a631fc7 100644 --- a/exercises/practice/flatten-array/.meta/template.j2 +++ b/exercises/practice/flatten-array/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/flatten-array/flatten_array_test.py b/exercises/practice/flatten-array/flatten_array_test.py index 4c69ab1f7c..cecb3c5633 100644 --- a/exercises/practice/flatten-array/flatten_array_test.py +++ b/exercises/practice/flatten-array/flatten_array_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flatten-array/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/food-chain/.meta/template.j2 b/exercises/practice/food-chain/.meta/template.j2 index 619ada74c2..bc5c61d571 100644 --- a/exercises/practice/food-chain/.meta/template.j2 +++ b/exercises/practice/food-chain/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/food-chain/food_chain_test.py b/exercises/practice/food-chain/food_chain_test.py index 1124f4f47c..0cd42356ab 100644 --- a/exercises/practice/food-chain/food_chain_test.py +++ b/exercises/practice/food-chain/food_chain_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/food-chain/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/forth/.meta/template.j2 b/exercises/practice/forth/.meta/template.j2 index b6fe07e71a..0b5dd2b89e 100644 --- a/exercises/practice/forth/.meta/template.j2 +++ b/exercises/practice/forth/.meta/template.j2 @@ -1,6 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{% set imports = ["evaluate", "StackUnderflowError"] %} -{{ macros.header(imports=imports, ignore=ignore) }} +{{ macros.canonical_ref() }} + +{{ macros.header(imports = ["evaluate", "StackUnderflowError"])}} {% macro test_case(group, case) -%} def test_{{ group | to_snake }}_{{ case["description"] | to_snake }}(self): diff --git a/exercises/practice/forth/forth_test.py b/exercises/practice/forth/forth_test.py index f4aa468d30..f8402bdd64 100644 --- a/exercises/practice/forth/forth_test.py +++ b/exercises/practice/forth/forth_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/gigasecond/.meta/template.j2 b/exercises/practice/gigasecond/.meta/template.j2 index 87b0a14843..e40462e2d8 100644 --- a/exercises/practice/gigasecond/.meta/template.j2 +++ b/exercises/practice/gigasecond/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + from datetime import datetime {{ macros.header() }} diff --git a/exercises/practice/gigasecond/gigasecond_test.py b/exercises/practice/gigasecond/gigasecond_test.py index b7ebed264e..dd648e14bd 100644 --- a/exercises/practice/gigasecond/gigasecond_test.py +++ b/exercises/practice/gigasecond/gigasecond_test.py @@ -1,9 +1,8 @@ -from datetime import datetime - # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/gigasecond/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 +from datetime import datetime import unittest from gigasecond import ( diff --git a/exercises/practice/go-counting/go_counting_test.py b/exercises/practice/go-counting/go_counting_test.py index 43eaa5b246..aec80a1b36 100644 --- a/exercises/practice/go-counting/go_counting_test.py +++ b/exercises/practice/go-counting/go_counting_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/go-counting/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest @@ -85,11 +85,3 @@ def test_two_region_rectangular_board(self): self.assertSetEqual(territories[BLACK], {(0, 0), (2, 0)}) self.assertSetEqual(territories[WHITE], set()) self.assertSetEqual(territories[NONE], set()) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/grade-school/.meta/template.j2 b/exercises/practice/grade-school/.meta/template.j2 index 8a86c1a8df..a97fa010db 100644 --- a/exercises/practice/grade-school/.meta/template.j2 +++ b/exercises/practice/grade-school/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["School"]) }} + {%- macro test_case( case) -%} {%- set input = case["input"] -%} {%- set property = case["property"] -%} @@ -15,8 +19,7 @@ {% else %} self.assertEqual(school.{{ case["property"] | to_snake }}(), expected) {%- endif %} -{% endmacro -%} -{{ macros.header(["School"]) }} +{% endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/grade-school/grade_school_test.py b/exercises/practice/grade-school/grade_school_test.py index 3f61d1303d..30d91c6c57 100644 --- a/exercises/practice/grade-school/grade_school_test.py +++ b/exercises/practice/grade-school/grade_school_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grade-school/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/grains/.meta/template.j2 b/exercises/practice/grains/.meta/template.j2 index 5ea2b5b0f8..18180801d6 100644 --- a/exercises/practice/grains/.meta/template.j2 +++ b/exercises/practice/grains/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} {% macro test_case(case) %} {%- set input = case["input"] %} @@ -16,8 +19,6 @@ {% endif %} {%- endmacro %} -{{ macros.header()}} - class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} {%- if "cases" in case -%} diff --git a/exercises/practice/grains/grains_test.py b/exercises/practice/grains/grains_test.py index 5820aa4ce1..8d729fb6f8 100644 --- a/exercises/practice/grains/grains_test.py +++ b/exercises/practice/grains/grains_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/grep/.meta/template.j2 b/exercises/practice/grep/.meta/template.j2 index 593bbc0781..fdb09b6dbf 100644 --- a/exercises/practice/grep/.meta/template.j2 +++ b/exercises/practice/grep/.meta/template.j2 @@ -1,6 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + import io +{{ macros.header()}} from unittest import mock {% set filenames = comments | join("\n") | regex_find("[a-z-]*\.txt") -%} diff --git a/exercises/practice/grep/grep_test.py b/exercises/practice/grep/grep_test.py index d15c2f3909..8195288456 100644 --- a/exercises/practice/grep/grep_test.py +++ b/exercises/practice/grep/grep_test.py @@ -1,13 +1,13 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grep/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 +import io import unittest from grep import ( grep, ) -import io from unittest import mock FILE_TEXT = { diff --git a/exercises/practice/hamming/.meta/template.j2 b/exercises/practice/hamming/.meta/template.j2 index 498fbc56ce..23efd5b73e 100644 --- a/exercises/practice/hamming/.meta/template.j2 +++ b/exercises/practice/hamming/.meta/template.j2 @@ -1,12 +1,15 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {%- macro test_call(case) %} {{ case["property"] }}( {% for arg in case["input"].values() -%} "{{ arg }}"{{ "," if not loop.last }} {% endfor %} ) -{% endmacro -%} -{{ macros.header() }} +{% endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/hamming/hamming_test.py b/exercises/practice/hamming/hamming_test.py index d9171a4449..df58ef002d 100644 --- a/exercises/practice/hamming/hamming_test.py +++ b/exercises/practice/hamming/hamming_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/hamming/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/hello-world/.meta/template.j2 b/exercises/practice/hello-world/.meta/template.j2 index dbcde585e9..600120d665 100644 --- a/exercises/practice/hello-world/.meta/template.j2 +++ b/exercises/practice/hello-world/.meta/template.j2 @@ -1,4 +1,5 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} import unittest diff --git a/exercises/practice/hello-world/hello_world_test.py b/exercises/practice/hello-world/hello_world_test.py index 245f5434fa..15473f5557 100644 --- a/exercises/practice/hello-world/hello_world_test.py +++ b/exercises/practice/hello-world/hello_world_test.py @@ -1,3 +1,7 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/hello-world/canonical-data.json +# File last updated on 2023-07-19 + import unittest try: diff --git a/exercises/practice/high-scores/.meta/template.j2 b/exercises/practice/high-scores/.meta/template.j2 index c32ae121ae..36e218c5f4 100644 --- a/exercises/practice/high-scores/.meta/template.j2 +++ b/exercises/practice/high-scores/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["HighScores"]) }} + {%- macro get_property(property) %} {%- if property == "scores" %} scores @@ -19,10 +23,7 @@ highscores.personal_{{ function }}() self.assertEqual(highscores.{{ get_property(property) }}, expected) {% endif -%} -{% endmacro -%} - -{{ macros.header(["HighScores"]) }} - +{% endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {%- for case in cases -%} diff --git a/exercises/practice/high-scores/high_scores_test.py b/exercises/practice/high-scores/high_scores_test.py index 46f14477f0..6a926e32fd 100644 --- a/exercises/practice/high-scores/high_scores_test.py +++ b/exercises/practice/high-scores/high_scores_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/high-scores/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/house/.meta/template.j2 b/exercises/practice/house/.meta/template.j2 index b855685dfd..6e61bc88ec 100644 --- a/exercises/practice/house/.meta/template.j2 +++ b/exercises/practice/house/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/house/house_test.py b/exercises/practice/house/house_test.py index a64809f83a..d859fb4f5e 100644 --- a/exercises/practice/house/house_test.py +++ b/exercises/practice/house/house_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/house/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/isbn-verifier/.meta/template.j2 b/exercises/practice/isbn-verifier/.meta/template.j2 index 47392cfb29..eae9dbc114 100644 --- a/exercises/practice/isbn-verifier/.meta/template.j2 +++ b/exercises/practice/isbn-verifier/.meta/template.j2 @@ -1,6 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} -{{ macros.header() }} +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/isbn-verifier/isbn_verifier_test.py b/exercises/practice/isbn-verifier/isbn_verifier_test.py index 341afea5f3..dbcddf19d4 100644 --- a/exercises/practice/isbn-verifier/isbn_verifier_test.py +++ b/exercises/practice/isbn-verifier/isbn_verifier_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isbn-verifier/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/isogram/.meta/template.j2 b/exercises/practice/isogram/.meta/template.j2 index fbec4a93a8..d75c2f4904 100644 --- a/exercises/practice/isogram/.meta/template.j2 +++ b/exercises/practice/isogram/.meta/template.j2 @@ -1,12 +1,15 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {%- macro test_call(case) %} {{ case["property"] | to_snake }}( {% for arg in case["input"].values() -%} "{{ arg }}"{{ "," if not loop.last }} {% endfor %} ) -{% endmacro -%} -{{ macros.header() }} +{% endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {# All test cases in this exercise are nested, so use two for loops -#} diff --git a/exercises/practice/isogram/isogram_test.py b/exercises/practice/isogram/isogram_test.py index 09d7431d15..c65984f6e6 100644 --- a/exercises/practice/isogram/isogram_test.py +++ b/exercises/practice/isogram/isogram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isogram/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/killer-sudoku-helper/.meta/template.j2 b/exercises/practice/killer-sudoku-helper/.meta/template.j2 index c107933784..43fca4aa99 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/template.j2 +++ b/exercises/practice/killer-sudoku-helper/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -8,7 +12,6 @@ {{ case["input"]["cage"]["exclude"] }}), {{ case["expected"] }}) {%- endmacro %} -{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases[0]["cases"] -%} diff --git a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py index 023687a9bc..2479bfc895 100644 --- a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py +++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/killer-sudoku-helper/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/kindergarten-garden/.meta/template.j2 b/exercises/practice/kindergarten-garden/.meta/template.j2 index f1dc54254d..4ba100bbe6 100644 --- a/exercises/practice/kindergarten-garden/.meta/template.j2 +++ b/exercises/practice/kindergarten-garden/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["Garden"]) }} + {%- macro test_case(group_name, case) -%} {%- set input = case["input"] -%} def test_{{ group_name | to_snake }}_ @@ -16,8 +20,7 @@ "{{ val | camel_case }}"{{- "," if not loop.last }} {% endfor %}] ) -{% endmacro -%} -{{ macros.header(["Garden"]) }} +{% endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for casegroup in cases %} diff --git a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py index 138cbb0792..fd4d022238 100644 --- a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py +++ b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/kindergarten-garden/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/knapsack/.meta/template.j2 b/exercises/practice/knapsack/.meta/template.j2 index 3c59a9e89c..e4282e9ce3 100644 --- a/exercises/practice/knapsack/.meta/template.j2 +++ b/exercises/practice/knapsack/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/knapsack/knapsack_test.py b/exercises/practice/knapsack/knapsack_test.py index 5ce2abae03..011f7ba832 100644 --- a/exercises/practice/knapsack/knapsack_test.py +++ b/exercises/practice/knapsack/knapsack_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/largest-series-product/.meta/template.j2 b/exercises/practice/largest-series-product/.meta/template.j2 index 7da257f4c2..07fe947fb5 100644 --- a/exercises/practice/largest-series-product/.meta/template.j2 +++ b/exercises/practice/largest-series-product/.meta/template.j2 @@ -1,9 +1,11 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {%- macro test_call(case) -%} {{ case["property"] | to_snake }}("{{ case["input"]["digits"] }}", {{ case["input"]["span"] }}) -{%- endmacro -%} - -{{ macros.header() }} +{%- endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/largest-series-product/largest_series_product_test.py b/exercises/practice/largest-series-product/largest_series_product_test.py index 3d5a90858a..e505623673 100644 --- a/exercises/practice/largest-series-product/largest_series_product_test.py +++ b/exercises/practice/largest-series-product/largest_series_product_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/largest-series-product/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/leap/.meta/template.j2 b/exercises/practice/leap/.meta/template.j2 index 66516c2b0d..d3607dcd3e 100644 --- a/exercises/practice/leap/.meta/template.j2 +++ b/exercises/practice/leap/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/leap/leap_test.py b/exercises/practice/leap/leap_test.py index 69f5387962..6a1d732c98 100644 --- a/exercises/practice/leap/leap_test.py +++ b/exercises/practice/leap/leap_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/ledger/.meta/template.j2 b/exercises/practice/ledger/.meta/template.j2 index 8936a2ca9a..a3dbcf26f7 100644 --- a/exercises/practice/ledger/.meta/template.j2 +++ b/exercises/practice/ledger/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(imports=["format_entries", "create_entry"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/ledger/ledger_test.py b/exercises/practice/ledger/ledger_test.py index d9d1027d99..cc37167146 100644 --- a/exercises/practice/ledger/ledger_test.py +++ b/exercises/practice/ledger/ledger_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/ledger/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index a753efc3df..604c5f5163 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["LinkedList"]) }} + {% macro test_case(case) -%} def test_{{ case["description"] | to_snake }}(self): lst = LinkedList() @@ -45,7 +49,6 @@ {%- endif %} {%- endmacro %} -{{ macros.header(["LinkedList"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index c73175609a..6724a1ebce 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/linked-list/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index 3ce6a3033b..2e0a1bdd63 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(imports=["append", "concat", "foldl", "foldr", "length", "reverse", "filter as list_ops_filter", "map as list_ops_map"]) }} + {% macro lambdify(function) -%} {% set function = function.replace("(", "", 1).replace(")", "", 1).replace(" ->", ":") %} {% set function = function.replace("modulo", "%") %} @@ -36,7 +40,6 @@ {{ stringify(case["expected"]) }} ) {%- endmacro %} -{{ macros.header(imports=["append", "concat", "foldl", "foldr", "length", "reverse", "filter as list_ops_filter", "map as list_ops_map"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for casegroup in cases -%} diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py index 0375d87564..ea0d213659 100644 --- a/exercises/practice/list-ops/list_ops_test.py +++ b/exercises/practice/list-ops/list_ops_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/list-ops/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/luhn/.meta/template.j2 b/exercises/practice/luhn/.meta/template.j2 index af8489b3c8..568d26efc6 100644 --- a/exercises/practice/luhn/.meta/template.j2 +++ b/exercises/practice/luhn/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["Luhn"]) }} + {%- macro test_case(group_name, case) -%} {%- set input = case["input"] -%} def test_{{ group_name | to_snake }}_ @@ -16,8 +20,7 @@ "{{ val | camel_case }}", {% endfor %}] ) -{% endmacro -%} -{{ macros.header(["Luhn"]) }} +{% endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/luhn/luhn_test.py b/exercises/practice/luhn/luhn_test.py index a83c3999d4..58234eb7d5 100644 --- a/exercises/practice/luhn/luhn_test.py +++ b/exercises/practice/luhn/luhn_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/luhn/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/markdown/.meta/template.j2 b/exercises/practice/markdown/.meta/template.j2 index da2a037cec..e0b5f717c7 100644 --- a/exercises/practice/markdown/.meta/template.j2 +++ b/exercises/practice/markdown/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/markdown/markdown_test.py b/exercises/practice/markdown/markdown_test.py index 7d474900fc..ad6d243a63 100644 --- a/exercises/practice/markdown/markdown_test.py +++ b/exercises/practice/markdown/markdown_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/markdown/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/matching-brackets/.meta/template.j2 b/exercises/practice/matching-brackets/.meta/template.j2 index 0d43598bfd..53abdc8efb 100644 --- a/exercises/practice/matching-brackets/.meta/template.j2 +++ b/exercises/practice/matching-brackets/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/matching-brackets/matching_brackets_test.py b/exercises/practice/matching-brackets/matching_brackets_test.py index e9693d23cd..a8321d94ad 100644 --- a/exercises/practice/matching-brackets/matching_brackets_test.py +++ b/exercises/practice/matching-brackets/matching_brackets_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/matching-brackets/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/matrix/.meta/template.j2 b/exercises/practice/matrix/.meta/template.j2 index ae1c08c3b6..090f7d85c8 100644 --- a/exercises/practice/matrix/.meta/template.j2 +++ b/exercises/practice/matrix/.meta/template.j2 @@ -1,16 +1,15 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["Matrix"])}} + {%- macro testcase(case) %} def test_{{ case["description"] | to_snake }}(self): matrix = Matrix("{{ case["input"]["string"] | replace('\n', '\\n') }}") self.assertEqual(matrix.{{ case["property"] | to_snake }}({{ case["input"]["index"]}} ), {{ case["expected"] }}) -{% endmacro -%} -{{ macros.header(["Matrix"])}} +{% endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {%- for case in cases -%} {{- testcase(case) -}} {% endfor %} - - -if __name__ == '__main__': - unittest.main() diff --git a/exercises/practice/matrix/matrix_test.py b/exercises/practice/matrix/matrix_test.py index 421f5bf6f7..6f5ce7da5f 100644 --- a/exercises/practice/matrix/matrix_test.py +++ b/exercises/practice/matrix/matrix_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/matrix/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest @@ -41,7 +41,3 @@ def test_can_extract_column_from_non_square_matrix_with_no_corresponding_row(sel def test_extract_column_where_numbers_have_different_widths(self): matrix = Matrix("89 1903 3\n18 3 1\n9 4 800") self.assertEqual(matrix.column(2), [1903, 3, 4]) - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/meetup/.meta/template.j2 b/exercises/practice/meetup/.meta/template.j2 index 39d9cf8db8..8078c5a3a3 100644 --- a/exercises/practice/meetup/.meta/template.j2 +++ b/exercises/practice/meetup/.meta/template.j2 @@ -1,4 +1,9 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +from datetime import date +{{ macros.header(imports=["meetup", "MeetupDayException"]) }} + {% macro test_case(case) -%} def test_{{ case["description"] | to_snake }}(self): {%- set input = namespace() %} @@ -18,9 +23,6 @@ {% endif %} {%- endmacro %} -from datetime import date -{{ macros.header(imports=["meetup", "MeetupDayException"]) }} - class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} {{ test_case(case) }} diff --git a/exercises/practice/meetup/meetup_test.py b/exercises/practice/meetup/meetup_test.py index c9d5ce0344..ec9e22b556 100644 --- a/exercises/practice/meetup/meetup_test.py +++ b/exercises/practice/meetup/meetup_test.py @@ -1,9 +1,8 @@ -from datetime import date - # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/meetup/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 +from datetime import date import unittest from meetup import ( diff --git a/exercises/practice/minesweeper/.meta/template.j2 b/exercises/practice/minesweeper/.meta/template.j2 index b34be79573..68570a9fd4 100644 --- a/exercises/practice/minesweeper/.meta/template.j2 +++ b/exercises/practice/minesweeper/.meta/template.j2 @@ -1,8 +1,11 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {%- macro test_call(case) -%} {{ case["property"] | to_snake }}({{ case["input"]["minefield"] }}) -{%- endmacro -%} -{{ macros.header() }} +{%- endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/minesweeper/minesweeper_test.py b/exercises/practice/minesweeper/minesweeper_test.py index 192cb40bc3..f6ffab4360 100644 --- a/exercises/practice/minesweeper/minesweeper_test.py +++ b/exercises/practice/minesweeper/minesweeper_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/minesweeper/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/nth-prime/.meta/template.j2 b/exercises/practice/nth-prime/.meta/template.j2 index 453ade5889..ab3d42adff 100644 --- a/exercises/practice/nth-prime/.meta/template.j2 +++ b/exercises/practice/nth-prime/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -14,7 +18,6 @@ ) {%- endif %} {%- endmacro %} -{{ macros.header()}} def prime_range(n): """Returns a list of the first n primes""" diff --git a/exercises/practice/nth-prime/nth_prime_test.py b/exercises/practice/nth-prime/nth_prime_test.py index ce42da6db3..6f30f30aa0 100644 --- a/exercises/practice/nth-prime/nth_prime_test.py +++ b/exercises/practice/nth-prime/nth_prime_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/nth-prime/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/ocr-numbers/.meta/template.j2 b/exercises/practice/ocr-numbers/.meta/template.j2 index c829331802..94286b1ef8 100644 --- a/exercises/practice/ocr-numbers/.meta/template.j2 +++ b/exercises/practice/ocr-numbers/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/ocr-numbers/ocr_numbers_test.py b/exercises/practice/ocr-numbers/ocr_numbers_test.py index c7f058f103..3d24adc557 100644 --- a/exercises/practice/ocr-numbers/ocr_numbers_test.py +++ b/exercises/practice/ocr-numbers/ocr_numbers_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/ocr-numbers/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/palindrome-products/.meta/template.j2 b/exercises/practice/palindrome-products/.meta/template.j2 index 634adaf53f..f1862fc0c8 100644 --- a/exercises/practice/palindrome-products/.meta/template.j2 +++ b/exercises/practice/palindrome-products/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} {%- macro value_factor_unpacking(case) -%} {%- set input = case["input"] -%} @@ -24,8 +27,6 @@ {%- endif %} {% endmacro %} -{{ macros.header() }} - class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} {{ test_case(case) }} diff --git a/exercises/practice/palindrome-products/palindrome_products_test.py b/exercises/practice/palindrome-products/palindrome_products_test.py index cbe5affe20..e9339b5d25 100644 --- a/exercises/practice/palindrome-products/palindrome_products_test.py +++ b/exercises/practice/palindrome-products/palindrome_products_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/palindrome-products/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/pangram/.meta/template.j2 b/exercises/practice/pangram/.meta/template.j2 index a365b45686..df38b57877 100644 --- a/exercises/practice/pangram/.meta/template.j2 +++ b/exercises/practice/pangram/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -7,7 +11,6 @@ {{ case["expected"] }} ) {%- endmacro %} -{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {# All test cases in this exercise are nested, so use two for loops -#} diff --git a/exercises/practice/pangram/pangram_test.py b/exercises/practice/pangram/pangram_test.py index 867660aaa8..09e303d440 100644 --- a/exercises/practice/pangram/pangram_test.py +++ b/exercises/practice/pangram/pangram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pangram/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/pascals-triangle/.meta/template.j2 b/exercises/practice/pascals-triangle/.meta/template.j2 index 0041fd53c2..936db7587c 100644 --- a/exercises/practice/pascals-triangle/.meta/template.j2 +++ b/exercises/practice/pascals-triangle/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + import sys {{ macros.header() }} diff --git a/exercises/practice/pascals-triangle/pascals_triangle_test.py b/exercises/practice/pascals-triangle/pascals_triangle_test.py index 972ee660ff..21f6889552 100644 --- a/exercises/practice/pascals-triangle/pascals_triangle_test.py +++ b/exercises/practice/pascals-triangle/pascals_triangle_test.py @@ -1,9 +1,8 @@ -import sys - # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pascals-triangle/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 +import sys import unittest from pascals_triangle import ( diff --git a/exercises/practice/perfect-numbers/.meta/template.j2 b/exercises/practice/perfect-numbers/.meta/template.j2 index c92e39ca40..e6cd031004 100644 --- a/exercises/practice/perfect-numbers/.meta/template.j2 +++ b/exercises/practice/perfect-numbers/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -14,7 +18,6 @@ ) {% endif %} {%- endmacro %} -{{ macros.header()}} {% for case in cases -%} class {{ case["description"] | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/perfect-numbers/perfect_numbers_test.py b/exercises/practice/perfect-numbers/perfect_numbers_test.py index 2f4f9a9bbc..eef8661cef 100644 --- a/exercises/practice/perfect-numbers/perfect_numbers_test.py +++ b/exercises/practice/perfect-numbers/perfect_numbers_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/perfect-numbers/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/phone-number/.meta/template.j2 b/exercises/practice/phone-number/.meta/template.j2 index 2b2826217c..faff6e206a 100644 --- a/exercises/practice/phone-number/.meta/template.j2 +++ b/exercises/practice/phone-number/.meta/template.j2 @@ -1,7 +1,10 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {% set class = exercise | camel_case -%} {{ macros.header([class]) }} + class {{ class }}Test(unittest.TestCase): {% for case in cases -%} def test_{{ case["description"] | to_snake }}(self): diff --git a/exercises/practice/phone-number/phone_number_test.py b/exercises/practice/phone-number/phone_number_test.py index c7a1006ead..2b018dfaaf 100644 --- a/exercises/practice/phone-number/phone_number_test.py +++ b/exercises/practice/phone-number/phone_number_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/phone-number/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/pig-latin/.meta/template.j2 b/exercises/practice/pig-latin/.meta/template.j2 index fd284824ff..5badcb774f 100644 --- a/exercises/practice/pig-latin/.meta/template.j2 +++ b/exercises/practice/pig-latin/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases %} diff --git a/exercises/practice/pig-latin/pig_latin_test.py b/exercises/practice/pig-latin/pig_latin_test.py index c3d0a20d36..e5a441eb6b 100644 --- a/exercises/practice/pig-latin/pig_latin_test.py +++ b/exercises/practice/pig-latin/pig_latin_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/poker/.meta/template.j2 b/exercises/practice/poker/.meta/template.j2 index 86e2e8791a..730d54dd69 100644 --- a/exercises/practice/poker/.meta/template.j2 +++ b/exercises/practice/poker/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py index 65e64867f5..c80aad6f05 100644 --- a/exercises/practice/poker/poker_test.py +++ b/exercises/practice/poker/poker_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/poker/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/pov/.meta/template.j2 b/exercises/practice/pov/.meta/template.j2 index aee59684a1..00d1f005a3 100644 --- a/exercises/practice/pov/.meta/template.j2 +++ b/exercises/practice/pov/.meta/template.j2 @@ -1,6 +1,9 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} -{%- macro test_case(case) %} +{{ macros.header(["Tree"]) }} + +{% macro test_case(case) %} def test_{{ case["description"] | to_snake }}(self): tree = {{ write_tree(case["input"]["tree"]) }} {% if case["property"] == "fromPov" -%} @@ -8,7 +11,7 @@ {%- elif case["property"] == "pathTo" -%} {{ test_path_to(case) }} {%- endif -%} -{% endmacro -%} +{% endmacro %} {%- macro test_from_pov(case) -%} {% if case["expected"] -%} @@ -20,7 +23,7 @@ self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "Tree could not be reoriented") {% endif -%} -{% endmacro -%} +{% endmacro %} {%- macro test_path_to(case) -%} {% if case["expected"] -%} @@ -47,8 +50,6 @@ ]{% endif %}) {%- endmacro -%} -{{ macros.header(["Tree"]) }} - class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases -%} {%- for case in supercase["cases"] %} diff --git a/exercises/practice/pov/pov_test.py b/exercises/practice/pov/pov_test.py index a43d104234..2436ebc2db 100644 --- a/exercises/practice/pov/pov_test.py +++ b/exercises/practice/pov/pov_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pov/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/prime-factors/.meta/template.j2 b/exercises/practice/prime-factors/.meta/template.j2 index 4178af79aa..cc9a9fe384 100644 --- a/exercises/practice/prime-factors/.meta/template.j2 +++ b/exercises/practice/prime-factors/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/prime-factors/prime_factors_test.py b/exercises/practice/prime-factors/prime_factors_test.py index d0554518c8..4f6865036e 100644 --- a/exercises/practice/prime-factors/prime_factors_test.py +++ b/exercises/practice/prime-factors/prime_factors_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/prime-factors/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/protein-translation/.meta/template.j2 b/exercises/practice/protein-translation/.meta/template.j2 index a8391c37ff..e0b591609f 100644 --- a/exercises/practice/protein-translation/.meta/template.j2 +++ b/exercises/practice/protein-translation/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/protein-translation/protein_translation_test.py b/exercises/practice/protein-translation/protein_translation_test.py index cb87652d0b..91e6324b6e 100644 --- a/exercises/practice/protein-translation/protein_translation_test.py +++ b/exercises/practice/protein-translation/protein_translation_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 index cf50a26190..333a2d47d7 100644 --- a/exercises/practice/proverb/.meta/template.j2 +++ b/exercises/practice/proverb/.meta/template.j2 @@ -1,5 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(imports=["proverb"]) }} + {{ "# PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.**" }} {{ "# A new line in a result list below **does not** always equal a new list element." }} {{ "# Check comma placement carefully!" }} diff --git a/exercises/practice/proverb/proverb_test.py b/exercises/practice/proverb/proverb_test.py index 6e36151bf9..8c09283c02 100644 --- a/exercises/practice/proverb/proverb_test.py +++ b/exercises/practice/proverb/proverb_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/proverb/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/pythagorean-triplet/.meta/template.j2 b/exercises/practice/pythagorean-triplet/.meta/template.j2 index 20d750c774..96fa266789 100644 --- a/exercises/practice/pythagorean-triplet/.meta/template.j2 +++ b/exercises/practice/pythagorean-triplet/.meta/template.j2 @@ -1,6 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} -{{ macros.header() }} +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py index c9307e4461..9113793224 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py +++ b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pythagorean-triplet/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/queen-attack/.meta/template.j2 b/exercises/practice/queen-attack/.meta/template.j2 index 6a08c5251e..b8be7aeaaf 100644 --- a/exercises/practice/queen-attack/.meta/template.j2 +++ b/exercises/practice/queen-attack/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(imports=['Queen']) }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/queen-attack/queen_attack_test.py b/exercises/practice/queen-attack/queen_attack_test.py index 1552085624..e1289aebce 100644 --- a/exercises/practice/queen-attack/queen_attack_test.py +++ b/exercises/practice/queen-attack/queen_attack_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/rail-fence-cipher/.meta/template.j2 b/exercises/practice/rail-fence-cipher/.meta/template.j2 index 2698af337c..4df5b2c66d 100644 --- a/exercises/practice/rail-fence-cipher/.meta/template.j2 +++ b/exercises/practice/rail-fence-cipher/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases %} diff --git a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py index e5466ee599..f82066ca27 100644 --- a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py +++ b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rail-fence-cipher/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/raindrops/.meta/template.j2 b/exercises/practice/raindrops/.meta/template.j2 index 1e025b8bf1..c84975c9bb 100644 --- a/exercises/practice/raindrops/.meta/template.j2 +++ b/exercises/practice/raindrops/.meta/template.j2 @@ -1,12 +1,15 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {%- macro test_call(case) %} {{ case["property"] | to_snake }}( {% for arg in case["input"].values() -%} {{ arg }}{{- "," if not loop.last }} {% endfor %} ) -{% endmacro -%} -{{ macros.header() }} +{% endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/raindrops/raindrops_test.py b/exercises/practice/raindrops/raindrops_test.py index d84dd175b8..b07e70dc24 100644 --- a/exercises/practice/raindrops/raindrops_test.py +++ b/exercises/practice/raindrops/raindrops_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/raindrops/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/rational-numbers/.meta/template.j2 b/exercises/practice/rational-numbers/.meta/template.j2 index 026bd1de48..eb640c9575 100644 --- a/exercises/practice/rational-numbers/.meta/template.j2 +++ b/exercises/practice/rational-numbers/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(imports=["Rational"]) }} {%- set operators = { "add": "+", @@ -82,9 +85,6 @@ {%- endmacro %} -from __future__ import division -{{ macros.header(imports=["Rational"]) }} - class {{ exercise | camel_case }}Test(unittest.TestCase): {% for mathtypescases in cases %} # Tests of type: {{ mathtypescases["description"] }} diff --git a/exercises/practice/rational-numbers/rational_numbers_test.py b/exercises/practice/rational-numbers/rational_numbers_test.py index 062b582d25..181bb128bf 100644 --- a/exercises/practice/rational-numbers/rational_numbers_test.py +++ b/exercises/practice/rational-numbers/rational_numbers_test.py @@ -1,8 +1,6 @@ -from __future__ import division - # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rational-numbers/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/react/.meta/template.j2 b/exercises/practice/react/.meta/template.j2 index f9fb637c43..f46e0f39e3 100644 --- a/exercises/practice/react/.meta/template.j2 +++ b/exercises/practice/react/.meta/template.j2 @@ -1,5 +1,9 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + from functools import partial +{{ macros.header(["InputCell", "ComputeCell"])}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} {%- set callback = [] -%} @@ -48,7 +52,6 @@ from functools import partial {%- endif %} {% endfor -%} {%- endmacro %} -{{ macros.header(["InputCell", "ComputeCell"])}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/react/react_test.py b/exercises/practice/react/react_test.py index ec54fa94cd..1f917e40b4 100644 --- a/exercises/practice/react/react_test.py +++ b/exercises/practice/react/react_test.py @@ -1,9 +1,8 @@ -from functools import partial - # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/react/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 +from functools import partial import unittest from react import ( diff --git a/exercises/practice/rectangles/.meta/template.j2 b/exercises/practice/rectangles/.meta/template.j2 index c9b1f623f7..0061968aa2 100644 --- a/exercises/practice/rectangles/.meta/template.j2 +++ b/exercises/practice/rectangles/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/rectangles/rectangles_test.py b/exercises/practice/rectangles/rectangles_test.py index 67c4d77ed4..89de9f2864 100644 --- a/exercises/practice/rectangles/rectangles_test.py +++ b/exercises/practice/rectangles/rectangles_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rectangles/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/resistor-color-duo/.meta/template.j2 b/exercises/practice/resistor-color-duo/.meta/template.j2 index cabf271cae..ac0a560ac0 100644 --- a/exercises/practice/resistor-color-duo/.meta/template.j2 +++ b/exercises/practice/resistor-color-duo/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -7,7 +11,6 @@ {{ case["expected"] }} ) {%- endmacro %} -{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py index b1bb371597..5a67016d89 100644 --- a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py +++ b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-duo/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/resistor-color-expert/.docs/instructions.md b/exercises/practice/resistor-color-expert/.docs/instructions.md index f08801644c..d76c567b5d 100644 --- a/exercises/practice/resistor-color-expert/.docs/instructions.md +++ b/exercises/practice/resistor-color-expert/.docs/instructions.md @@ -41,7 +41,8 @@ The four-band resistor is built up like this: Meaning - orange-orange-brown-green would be 330 ohms with a ±0.5% tolerance. -- orange-orange-red would-grey would be 3300 ohms with ±0.005% tolerance. +- orange-orange-red-grey would be 3300 ohms with ±0.05% tolerance. + The difference between a four and five-band resistor is that the five-band resistor has an extra band to indicate a more precise value. | Band_1 | Band_2 | Band_3 | Band_4 | band_5 | @@ -50,9 +51,11 @@ The difference between a four and five-band resistor is that the five-band resis Meaning -- orange-orange-orange-black-green would be 330 ohms with a ±0.5% tolerance. +- orange-orange-orange-black-green would be 333 ohms with a ±0.5% tolerance. +- orange-red-orange-blue-violet would be 323M ohms with a ±0.10 tolerance. + There are also one band resistors. -This type of resistor only has the color black and has a value of 0. +One band resistors only have the color black with a value of 0. This exercise is about translating the resistor band colors into a label: diff --git a/exercises/practice/resistor-color-trio/.meta/template.j2 b/exercises/practice/resistor-color-trio/.meta/template.j2 index 54814bed31..aa45845003 100644 --- a/exercises/practice/resistor-color-trio/.meta/template.j2 +++ b/exercises/practice/resistor-color-trio/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -7,7 +11,6 @@ "{{ case['expected']['value']}} {{ case['expected']['unit']}}" ) {%- endmacro %} -{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py index 90f5842b23..05f794281d 100644 --- a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py +++ b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-trio/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/resistor-color/.meta/template.j2 b/exercises/practice/resistor-color/.meta/template.j2 index 643a3b69f9..0a6830e8ca 100644 --- a/exercises/practice/resistor-color/.meta/template.j2 +++ b/exercises/practice/resistor-color/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/resistor-color/resistor_color_test.py b/exercises/practice/resistor-color/resistor_color_test.py index 4847708e80..d86d755321 100644 --- a/exercises/practice/resistor-color/resistor_color_test.py +++ b/exercises/practice/resistor-color/resistor_color_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/rest-api/.meta/template.j2 b/exercises/practice/rest-api/.meta/template.j2 index aa7afe8f4e..a467adb569 100644 --- a/exercises/practice/rest-api/.meta/template.j2 +++ b/exercises/practice/rest-api/.meta/template.j2 @@ -1,6 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header(imports=["RestAPI"]) }} +{{ macros.canonical_ref() }} + import json +{{ macros.header(imports=["RestAPI"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for casegroup in cases -%}{%- for case in casegroup["cases"] -%} diff --git a/exercises/practice/rest-api/rest_api_test.py b/exercises/practice/rest-api/rest_api_test.py index 2ef7247d40..6d0bd213a1 100644 --- a/exercises/practice/rest-api/rest_api_test.py +++ b/exercises/practice/rest-api/rest_api_test.py @@ -1,13 +1,13 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rest-api/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 +import json import unittest from rest_api import ( RestAPI, ) -import json class RestApiTest(unittest.TestCase): diff --git a/exercises/practice/reverse-string/.meta/template.j2 b/exercises/practice/reverse-string/.meta/template.j2 index f76cc28e4d..bf60c0643e 100644 --- a/exercises/practice/reverse-string/.meta/template.j2 +++ b/exercises/practice/reverse-string/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/reverse-string/reverse_string_test.py b/exercises/practice/reverse-string/reverse_string_test.py index 79b9f6829c..0c3298704c 100644 --- a/exercises/practice/reverse-string/reverse_string_test.py +++ b/exercises/practice/reverse-string/reverse_string_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/rna-transcription/.meta/template.j2 b/exercises/practice/rna-transcription/.meta/template.j2 index ebd1675187..04caf39d50 100644 --- a/exercises/practice/rna-transcription/.meta/template.j2 +++ b/exercises/practice/rna-transcription/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/rna-transcription/rna_transcription_test.py b/exercises/practice/rna-transcription/rna_transcription_test.py index 65bb147609..766f20299e 100644 --- a/exercises/practice/rna-transcription/rna_transcription_test.py +++ b/exercises/practice/rna-transcription/rna_transcription_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rna-transcription/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/robot-simulator/.meta/template.j2 b/exercises/practice/robot-simulator/.meta/template.j2 index e729773437..787ff40bb6 100644 --- a/exercises/practice/robot-simulator/.meta/template.j2 +++ b/exercises/practice/robot-simulator/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(imports=["Robot", "NORTH", "EAST", "SOUTH", "WEST"]) }} {% macro test_case(case) -%} {%- set input = case["input"] -%} @@ -15,7 +18,6 @@ self.assertEqual(robot.direction, {{case["expected"]["direction"] | upper }}) {%- endmacro %} -{{ macros.header(imports=["Robot", "NORTH", "EAST", "SOUTH", "WEST"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases %} diff --git a/exercises/practice/robot-simulator/robot_simulator_test.py b/exercises/practice/robot-simulator/robot_simulator_test.py index f401e7c35b..69825c1118 100644 --- a/exercises/practice/robot-simulator/robot_simulator_test.py +++ b/exercises/practice/robot-simulator/robot_simulator_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/robot-simulator/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/roman-numerals/.meta/template.j2 b/exercises/practice/roman-numerals/.meta/template.j2 index f85f619fa7..a972569f0f 100644 --- a/exercises/practice/roman-numerals/.meta/template.j2 +++ b/exercises/practice/roman-numerals/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(["roman"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/roman-numerals/roman_numerals_test.py b/exercises/practice/roman-numerals/roman_numerals_test.py index 2b64de76cb..d2120f9b15 100644 --- a/exercises/practice/roman-numerals/roman_numerals_test.py +++ b/exercises/practice/roman-numerals/roman_numerals_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/rotational-cipher/.meta/template.j2 b/exercises/practice/rotational-cipher/.meta/template.j2 index 7db7871ea6..94a54cf6a8 100644 --- a/exercises/practice/rotational-cipher/.meta/template.j2 +++ b/exercises/practice/rotational-cipher/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/rotational-cipher/rotational_cipher_test.py b/exercises/practice/rotational-cipher/rotational_cipher_test.py index db460f2d92..ca22735ef9 100644 --- a/exercises/practice/rotational-cipher/rotational_cipher_test.py +++ b/exercises/practice/rotational-cipher/rotational_cipher_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rotational-cipher/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/run-length-encoding/.meta/template.j2 b/exercises/practice/run-length-encoding/.meta/template.j2 index 54a4a5be61..6bd93167bc 100644 --- a/exercises/practice/run-length-encoding/.meta/template.j2 +++ b/exercises/practice/run-length-encoding/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(['encode','decode'])}} {% macro test_case(case, tmod) -%} {%- set input = case["input"] -%} @@ -13,7 +16,6 @@ ) {%- endmacro %} -{{ macros.header(['encode','decode'])}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases -%} diff --git a/exercises/practice/run-length-encoding/run_length_encoding_test.py b/exercises/practice/run-length-encoding/run_length_encoding_test.py index c3cccafb0b..8d65cb62e2 100644 --- a/exercises/practice/run-length-encoding/run_length_encoding_test.py +++ b/exercises/practice/run-length-encoding/run_length_encoding_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/saddle-points/.meta/template.j2 b/exercises/practice/saddle-points/.meta/template.j2 index 35e4ed56b9..d04e6d0e88 100644 --- a/exercises/practice/saddle-points/.meta/template.j2 +++ b/exercises/practice/saddle-points/.meta/template.j2 @@ -1,11 +1,7 @@ -"""Tests for the saddle-points exercise +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} -Implementation note: -The saddle_points function must validate the input matrix and raise a -ValueError with a meaningful error message if the matrix turns out to be -irregular. -""" -{% import "generator_macros.j2" as macros with context -%} +{{ macros.header()}} {% macro test_case(case) -%} {%- set input = case["input"] -%} @@ -29,7 +25,6 @@ irregular. {% endif -%} {% endmacro -%} -{{ macros.header()}} def sorted_points(point_list): return sorted(point_list, key=lambda p: (p["row"], p["column"])) diff --git a/exercises/practice/saddle-points/saddle_points_test.py b/exercises/practice/saddle-points/saddle_points_test.py index d972355527..78ddf2484d 100644 --- a/exercises/practice/saddle-points/saddle_points_test.py +++ b/exercises/practice/saddle-points/saddle_points_test.py @@ -1,13 +1,6 @@ -"""Tests for the saddle-points exercise - -Implementation note: -The saddle_points function must validate the input matrix and raise a -ValueError with a meaningful error message if the matrix turns out to be -irregular. -""" # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/saddle-points/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/satellite/.meta/template.j2 b/exercises/practice/satellite/.meta/template.j2 index 3bd41df23a..d8ee854568 100644 --- a/exercises/practice/satellite/.meta/template.j2 +++ b/exercises/practice/satellite/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/satellite/satellite_test.py b/exercises/practice/satellite/satellite_test.py index b7b23d02e3..f44a538479 100644 --- a/exercises/practice/satellite/satellite_test.py +++ b/exercises/practice/satellite/satellite_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/satellite/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/say/.meta/template.j2 b/exercises/practice/say/.meta/template.j2 index 77fa67a1ca..81d61a694c 100644 --- a/exercises/practice/say/.meta/template.j2 +++ b/exercises/practice/say/.meta/template.j2 @@ -1,12 +1,14 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} {%- macro test_call(case) %} {{ case["property"] }}( {{ case["input"]["number"] }} ) -{% endmacro -%} +{% endmacro %} -{{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/say/say_test.py b/exercises/practice/say/say_test.py index eaf7224612..1b9cbdea92 100644 --- a/exercises/practice/say/say_test.py +++ b/exercises/practice/say/say_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/say/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/scale-generator/.meta/template.j2 b/exercises/practice/scale-generator/.meta/template.j2 index da566b8abe..d802db09b4 100644 --- a/exercises/practice/scale-generator/.meta/template.j2 +++ b/exercises/practice/scale-generator/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["Scale"])}} {% macro test_case(case) -%} {%- set tonic = case["input"]["tonic"] -%} @@ -19,7 +22,6 @@ {% endfor %} {%- endmacro %} -{{ macros.header(["Scale"])}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for supercase in cases %} diff --git a/exercises/practice/scale-generator/scale_generator_test.py b/exercises/practice/scale-generator/scale_generator_test.py index 7cef230494..c2346119b6 100644 --- a/exercises/practice/scale-generator/scale_generator_test.py +++ b/exercises/practice/scale-generator/scale_generator_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/scale-generator/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/scrabble-score/.meta/template.j2 b/exercises/practice/scrabble-score/.meta/template.j2 index ea6c9ae224..0688c940fd 100644 --- a/exercises/practice/scrabble-score/.meta/template.j2 +++ b/exercises/practice/scrabble-score/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -7,7 +11,6 @@ {{ case["expected"] }} ) {%- endmacro %} -{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/scrabble-score/scrabble_score_test.py b/exercises/practice/scrabble-score/scrabble_score_test.py index aeadf63f68..7121cf640e 100644 --- a/exercises/practice/scrabble-score/scrabble_score_test.py +++ b/exercises/practice/scrabble-score/scrabble_score_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/scrabble-score/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/secret-handshake/.meta/template.j2 b/exercises/practice/secret-handshake/.meta/template.j2 index f5791a8bd8..b45136a57c 100644 --- a/exercises/practice/secret-handshake/.meta/template.j2 +++ b/exercises/practice/secret-handshake/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/secret-handshake/secret_handshake_test.py b/exercises/practice/secret-handshake/secret_handshake_test.py index 0f1e4887ea..544da4e271 100644 --- a/exercises/practice/secret-handshake/secret_handshake_test.py +++ b/exercises/practice/secret-handshake/secret_handshake_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/secret-handshake/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/series/.meta/template.j2 b/exercises/practice/series/.meta/template.j2 index 2e7f60e4fd..c9ac185ab0 100644 --- a/exercises/practice/series/.meta/template.j2 +++ b/exercises/practice/series/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/series/series_test.py b/exercises/practice/series/series_test.py index b69e3961cd..5e0a33d993 100644 --- a/exercises/practice/series/series_test.py +++ b/exercises/practice/series/series_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/series/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/sgf-parsing/.meta/template.j2 b/exercises/practice/sgf-parsing/.meta/template.j2 index 7017a43648..3e968bec53 100644 --- a/exercises/practice/sgf-parsing/.meta/template.j2 +++ b/exercises/practice/sgf-parsing/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(["parse","SgfTree"])}} {% macro escape_sequences(string) -%} {{ string | replace("\\", "\\\\") | replace("\n", "\\n") | replace("\t", "\\t") }} @@ -43,7 +46,6 @@ {% endfor -%} } {%- endmacro -%} -{{ macros.header(["parse","SgfTree"])}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/sgf-parsing/sgf_parsing_test.py b/exercises/practice/sgf-parsing/sgf_parsing_test.py index 8927a78972..c33a5dbecf 100644 --- a/exercises/practice/sgf-parsing/sgf_parsing_test.py +++ b/exercises/practice/sgf-parsing/sgf_parsing_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sgf-parsing/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/sieve/.meta/template.j2 b/exercises/practice/sieve/.meta/template.j2 index 68a9db82a6..d12df86bb1 100644 --- a/exercises/practice/sieve/.meta/template.j2 +++ b/exercises/practice/sieve/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/sieve/sieve_test.py b/exercises/practice/sieve/sieve_test.py index aea5ad1264..d9b8855831 100644 --- a/exercises/practice/sieve/sieve_test.py +++ b/exercises/practice/sieve/sieve_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sieve/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/simple-cipher/.meta/template.j2 b/exercises/practice/simple-cipher/.meta/template.j2 index ce9ad3e446..fa7483a559 100644 --- a/exercises/practice/simple-cipher/.meta/template.j2 +++ b/exercises/practice/simple-cipher/.meta/template.j2 @@ -1,6 +1,9 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + import re {{ macros.header(imports=['Cipher']) }} + {%- macro convert_js(s) -%} {{ s | regex_replace("\\.substring\\((\\d+), ([^)]+)\\)", "[\\1:\\2]") | diff --git a/exercises/practice/simple-cipher/simple_cipher_test.py b/exercises/practice/simple-cipher/simple_cipher_test.py index b3aa2ddb99..bc1efa9f86 100644 --- a/exercises/practice/simple-cipher/simple_cipher_test.py +++ b/exercises/practice/simple-cipher/simple_cipher_test.py @@ -1,9 +1,8 @@ -import re - # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/simple-cipher/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-20 +import re import unittest from simple_cipher import ( diff --git a/exercises/practice/space-age/.meta/template.j2 b/exercises/practice/space-age/.meta/template.j2 index 15c8c55032..d939487f8a 100644 --- a/exercises/practice/space-age/.meta/template.j2 +++ b/exercises/practice/space-age/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(["SpaceAge"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/space-age/space_age_test.py b/exercises/practice/space-age/space_age_test.py index e5bbad29d2..ddde365d6b 100644 --- a/exercises/practice/space-age/space_age_test.py +++ b/exercises/practice/space-age/space_age_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/space-age/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/spiral-matrix/.meta/template.j2 b/exercises/practice/spiral-matrix/.meta/template.j2 index 2a73e6bccb..552b48831c 100644 --- a/exercises/practice/spiral-matrix/.meta/template.j2 +++ b/exercises/practice/spiral-matrix/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/spiral-matrix/spiral_matrix_test.py b/exercises/practice/spiral-matrix/spiral_matrix_test.py index 53015c6528..4917445597 100644 --- a/exercises/practice/spiral-matrix/spiral_matrix_test.py +++ b/exercises/practice/spiral-matrix/spiral_matrix_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/spiral-matrix/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/square-root/.meta/template.j2 b/exercises/practice/square-root/.meta/template.j2 index 9334317859..9038eb171a 100644 --- a/exercises/practice/square-root/.meta/template.j2 +++ b/exercises/practice/square-root/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -7,7 +11,6 @@ {{ case["expected"] }} ) {%- endmacro %} -{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/square-root/square_root_test.py b/exercises/practice/square-root/square_root_test.py index 1bd1d96d43..8f94940f55 100644 --- a/exercises/practice/square-root/square_root_test.py +++ b/exercises/practice/square-root/square_root_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/sublist/.meta/template.j2 b/exercises/practice/sublist/.meta/template.j2 index 00c111f3fc..ecd410bdaa 100644 --- a/exercises/practice/sublist/.meta/template.j2 +++ b/exercises/practice/sublist/.meta/template.j2 @@ -1,10 +1,13 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(imports=["sublist", "SUBLIST", "SUPERLIST", "EQUAL", "UNEQUAL"]) }} + {% macro test_case(case) -%} def test_{{ case["description"] | to_snake }}(self): self.assertEqual({{ case["property"] }}({{ case["input"]["listOne"] }}, {{ case["input"]["listTwo"] }}), {{ case["expected"] | upper }}) {%- endmacro %} -{{ macros.header(imports=["sublist", "SUBLIST", "SUPERLIST", "EQUAL", "UNEQUAL"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/sublist/sublist_test.py b/exercises/practice/sublist/sublist_test.py index bb255c77d3..af9a7799c1 100644 --- a/exercises/practice/sublist/sublist_test.py +++ b/exercises/practice/sublist/sublist_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sublist/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/sum-of-multiples/.meta/template.j2 b/exercises/practice/sum-of-multiples/.meta/template.j2 index 39dab6ada1..03574ec7e8 100644 --- a/exercises/practice/sum-of-multiples/.meta/template.j2 +++ b/exercises/practice/sum-of-multiples/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(["sum_of_multiples"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py index 179fba71a5..0057ee7a7f 100644 --- a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py +++ b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sum-of-multiples/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/tournament/.meta/template.j2 b/exercises/practice/tournament/.meta/template.j2 index 30ca2e94cd..055b3a89d7 100644 --- a/exercises/practice/tournament/.meta/template.j2 +++ b/exercises/practice/tournament/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases %} diff --git a/exercises/practice/tournament/tournament_test.py b/exercises/practice/tournament/tournament_test.py index 45db6055bb..622983525d 100644 --- a/exercises/practice/tournament/tournament_test.py +++ b/exercises/practice/tournament/tournament_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/tournament/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2 index ef724132e8..e622947b10 100644 --- a/exercises/practice/transpose/.meta/template.j2 +++ b/exercises/practice/transpose/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/transpose/transpose_test.py b/exercises/practice/transpose/transpose_test.py index 4e4bee820d..d3ab85ff9f 100644 --- a/exercises/practice/transpose/transpose_test.py +++ b/exercises/practice/transpose/transpose_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/triangle/.meta/template.j2 b/exercises/practice/triangle/.meta/template.j2 index 6d79c8c398..b196cfc717 100644 --- a/exercises/practice/triangle/.meta/template.j2 +++ b/exercises/practice/triangle/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(["equilateral", "isosceles", "scalene"]) }} {% for case in cases -%} diff --git a/exercises/practice/triangle/triangle_test.py b/exercises/practice/triangle/triangle_test.py index 5c4263cf1b..b279c83c32 100644 --- a/exercises/practice/triangle/triangle_test.py +++ b/exercises/practice/triangle/triangle_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/twelve-days/.meta/template.j2 b/exercises/practice/twelve-days/.meta/template.j2 index e90fe71307..2baa27d2c4 100644 --- a/exercises/practice/twelve-days/.meta/template.j2 +++ b/exercises/practice/twelve-days/.meta/template.j2 @@ -1,5 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {{ "# PLEASE TAKE NOTE: Expected result lists for these test cases use **implicit line joining.**" }} {{ "# A new line in a result list below **does not** always equal a new list element." }} {{ "# Check comma placement carefully!" }} diff --git a/exercises/practice/twelve-days/twelve_days_test.py b/exercises/practice/twelve-days/twelve_days_test.py index c48ee421ac..b18c35e730 100644 --- a/exercises/practice/twelve-days/twelve_days_test.py +++ b/exercises/practice/twelve-days/twelve_days_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/twelve-days/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/two-bucket/.meta/template.j2 b/exercises/practice/two-bucket/.meta/template.j2 index 0284379127..2cd31976ea 100644 --- a/exercises/practice/two-bucket/.meta/template.j2 +++ b/exercises/practice/two-bucket/.meta/template.j2 @@ -1,11 +1,14 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {%- macro test_call(case) -%} {{ case["property"] }}({{ case["input"]["bucketOne"] }}, {{ case["input"]["bucketTwo"] }}, {{ case["input"]["goal"] }}, "{{ case["input"]["startBucket"] }}") -{%- endmacro -%} -{{ macros.header() }} +{%- endmacro %} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} @@ -24,4 +27,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% endfor %} -{{ macros.footer() }} +{{ macros.utility() }} \ No newline at end of file diff --git a/exercises/practice/two-bucket/two_bucket_test.py b/exercises/practice/two-bucket/two_bucket_test.py index 5b5841cec2..b7d1cc0195 100644 --- a/exercises/practice/two-bucket/two_bucket_test.py +++ b/exercises/practice/two-bucket/two_bucket_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-bucket/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-21 import unittest @@ -54,7 +54,3 @@ def test_goal_larger_than_both_buckets_is_impossible(self): # Utility functions def assertRaisesWithMessage(self, exception): return self.assertRaisesRegex(exception, r".+") - - -if __name__ == "__main__": - unittest.main() diff --git a/exercises/practice/two-fer/.meta/template.j2 b/exercises/practice/two-fer/.meta/template.j2 index 5db1d53c7e..794d09ad19 100644 --- a/exercises/practice/two-fer/.meta/template.j2 +++ b/exercises/practice/two-fer/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/two-fer/two_fer_test.py b/exercises/practice/two-fer/two_fer_test.py index f9e9760b2c..fa032f08e3 100644 --- a/exercises/practice/two-fer/two_fer_test.py +++ b/exercises/practice/two-fer/two_fer_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-fer/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/variable-length-quantity/.meta/template.j2 b/exercises/practice/variable-length-quantity/.meta/template.j2 index 10b43cbdcc..c95696ad48 100644 --- a/exercises/practice/variable-length-quantity/.meta/template.j2 +++ b/exercises/practice/variable-length-quantity/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} {%- macro list_int_to_hex(integers) %} [ @@ -6,9 +9,8 @@ {{ "0x{:x}".format(integer) }}{{- "," if not loop.last }} {% endfor %} ] -{% endmacro -%} +{% endmacro %} -{{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py index 032403d34d..baeb236543 100644 --- a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py +++ b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/word-count/.meta/template.j2 b/exercises/practice/word-count/.meta/template.j2 index 7e41be6776..7f3a622391 100644 --- a/exercises/practice/word-count/.meta/template.j2 +++ b/exercises/practice/word-count/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} {%- set input = case["input"] -%} def test_{{ case["description"] | to_snake }}(self): @@ -7,7 +11,6 @@ {{ case["expected"] }} ) {%- endmacro %} -{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/word-count/word_count_test.py b/exercises/practice/word-count/word_count_test.py index c620335921..1bf0bdf6dc 100644 --- a/exercises/practice/word-count/word_count_test.py +++ b/exercises/practice/word-count/word_count_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-count/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/word-search/.meta/template.j2 b/exercises/practice/word-search/.meta/template.j2 index 952da9e911..14051f12b3 100644 --- a/exercises/practice/word-search/.meta/template.j2 +++ b/exercises/practice/word-search/.meta/template.j2 @@ -1,4 +1,6 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + {{ macros.header(imports=["WordSearch", "Point"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/word-search/word_search_test.py b/exercises/practice/word-search/word_search_test.py index ba1c7a7761..d0cbd191d9 100644 --- a/exercises/practice/word-search/word_search_test.py +++ b/exercises/practice/word-search/word_search_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-search/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/wordy/.meta/template.j2 b/exercises/practice/wordy/.meta/template.j2 index 00b70a8bb1..74ab335ff9 100644 --- a/exercises/practice/wordy/.meta/template.j2 +++ b/exercises/practice/wordy/.meta/template.j2 @@ -1,4 +1,8 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + {% macro test_case(case) -%} def test_{{ case["description"] | to_snake }}(self): {%- set question = case["input"]["question"] %} @@ -11,7 +15,6 @@ self.assertEqual({{ case["property"] }}("{{ question }}"), {{ case["expected"] }}) {%- endif %} {%- endmacro %} -{{ macros.header() }} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/wordy/wordy_test.py b/exercises/practice/wordy/wordy_test.py index 7a32eba420..ffcaf49aed 100644 --- a/exercises/practice/wordy/wordy_test.py +++ b/exercises/practice/wordy/wordy_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/yacht/.meta/template.j2 b/exercises/practice/yacht/.meta/template.j2 index a9e3ebc4bc..af604a42f9 100644 --- a/exercises/practice/yacht/.meta/template.j2 +++ b/exercises/practice/yacht/.meta/template.j2 @@ -1,6 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -import unittest +{{ macros.canonical_ref() }} +import unittest import {{ exercise }} class {{ exercise | camel_case }}Test(unittest.TestCase): diff --git a/exercises/practice/yacht/yacht_test.py b/exercises/practice/yacht/yacht_test.py index 58566e0607..fd2f87ad1f 100644 --- a/exercises/practice/yacht/yacht_test.py +++ b/exercises/practice/yacht/yacht_test.py @@ -1,5 +1,8 @@ -import unittest +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/yacht/canonical-data.json +# File last updated on 2023-07-19 +import unittest import yacht diff --git a/exercises/practice/zebra-puzzle/.meta/template.j2 b/exercises/practice/zebra-puzzle/.meta/template.j2 index 134ccdbf48..493b7ba7ee 100644 --- a/exercises/practice/zebra-puzzle/.meta/template.j2 +++ b/exercises/practice/zebra-puzzle/.meta/template.j2 @@ -1,5 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} -{{ macros.header() }} +{{ macros.canonical_ref() }} + +{{ macros.header()}} class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} diff --git a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py index b64fc9069c..fd2a331b18 100644 --- a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py +++ b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/zebra-puzzle/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest diff --git a/exercises/practice/zipper/.meta/template.j2 b/exercises/practice/zipper/.meta/template.j2 index 4e5de99523..5d291d77f0 100644 --- a/exercises/practice/zipper/.meta/template.j2 +++ b/exercises/practice/zipper/.meta/template.j2 @@ -1,4 +1,7 @@ {%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header (imports=["Zipper"]) }} {%- macro test_case(case) %} {%- set input = case["input"] -%} @@ -9,7 +12,7 @@ {%- else %} {{ expected_value(input, expected) }} {%- endif %} -{%- endmacro -%} +{% endmacro %} {%- macro expected_value(input, expected) -%} initial = {{ input["initialTree"] }} @@ -44,9 +47,8 @@ {%- for op in operations -%} .{{ op["operation"] }}({{ op["item"] }}) {%- endfor %} -{%- endmacro -%} +{%- endmacro %} -{{ macros.header (imports=["Zipper"]) }} class {{ exercise | camel_case }}Test(unittest.TestCase): {%- for case in cases %} diff --git a/exercises/practice/zipper/zipper_test.py b/exercises/practice/zipper/zipper_test.py index 1035835835..702f6ccbcf 100644 --- a/exercises/practice/zipper/zipper_test.py +++ b/exercises/practice/zipper/zipper_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/zipper/canonical-data.json -# File last updated on 2023-07-16 +# File last updated on 2023-07-19 import unittest From c56ebb929e94b62814b63f24cba421203bf7f425 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 23 Jul 2023 08:11:00 -0700 Subject: [PATCH 501/826] Removed Yacht from bools association and pushed it to later in track. (#3471) --- config.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config.json b/config.json index ec35a0a9c3..7f6c13dc15 100644 --- a/config.json +++ b/config.json @@ -691,14 +691,6 @@ "unpacking-and-multiple-assignment"], "difficulty": 2 }, - { - "slug": "yacht", - "name": "Yacht", - "uuid": "22f937e5-52a7-4956-9dde-61c985251a6b", - "practices": ["bools"], - "prerequisites": ["basics", "bools", "numbers"], - "difficulty": 2 - }, { "slug": "robot-name", "name": "Robot Name", @@ -866,6 +858,14 @@ ], "difficulty": 2 }, + { + "slug": "yacht", + "name": "Yacht", + "uuid": "22f937e5-52a7-4956-9dde-61c985251a6b", + "practices": ["enums"], + "prerequisites": ["basics", "bools", "numbers", "classes"], + "difficulty": 2 + }, { "slug": "sublist", "name": "Sublist", From 4da9d964d2281f7dc4314b6826dc7700fe123ffd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 25 Jul 2023 10:25:51 -0700 Subject: [PATCH 502/826] Roadmap Updates (#3474) * Updates to tree and concept status. * Adjusted links to exercises and issues. * WIP fixes. * Fix Beta in Locomotive Engineer. * Renamed dataclasses and named tuples. --- .../.meta/config.json | 0 .../about.md | 0 .../introduction.md | 0 .../links.json | 0 config.json | 4 +- reference/track_exercises_overview.md | 519 ++++++++++-------- 6 files changed, 293 insertions(+), 230 deletions(-) rename concepts/{dataclasses-and-namedtuples => dataclasses}/.meta/config.json (100%) rename concepts/{dataclasses-and-namedtuples => dataclasses}/about.md (100%) rename concepts/{dataclasses-and-namedtuples => dataclasses}/introduction.md (100%) rename concepts/{dataclasses-and-namedtuples => dataclasses}/links.json (100%) diff --git a/concepts/dataclasses-and-namedtuples/.meta/config.json b/concepts/dataclasses/.meta/config.json similarity index 100% rename from concepts/dataclasses-and-namedtuples/.meta/config.json rename to concepts/dataclasses/.meta/config.json diff --git a/concepts/dataclasses-and-namedtuples/about.md b/concepts/dataclasses/about.md similarity index 100% rename from concepts/dataclasses-and-namedtuples/about.md rename to concepts/dataclasses/about.md diff --git a/concepts/dataclasses-and-namedtuples/introduction.md b/concepts/dataclasses/introduction.md similarity index 100% rename from concepts/dataclasses-and-namedtuples/introduction.md rename to concepts/dataclasses/introduction.md diff --git a/concepts/dataclasses-and-namedtuples/links.json b/concepts/dataclasses/links.json similarity index 100% rename from concepts/dataclasses-and-namedtuples/links.json rename to concepts/dataclasses/links.json diff --git a/config.json b/config.json index 7f6c13dc15..36f517f949 100644 --- a/config.json +++ b/config.json @@ -2335,8 +2335,8 @@ }, { "uuid": "ba20a459-99ac-4643-b386-8b90e9c94328", - "slug": "dataclasses-and-namedtuples", - "name": "Dataclasses And Namedtuples" + "slug": "dataclasses", + "name": "Dataclasses" }, { "uuid": "48ef77af-50f5-466e-a537-bcd016550058", diff --git a/reference/track_exercises_overview.md b/reference/track_exercises_overview.md index f7a0416803..761f2c4764 100644 --- a/reference/track_exercises_overview.md +++ b/reference/track_exercises_overview.md @@ -11,126 +11,128 @@ Practice Exercises with Difficulty, Solutions, and Mentor Notes
-| Exercise | Difficulty | Solutions | Prereqs | Practices | Mentor Notes | Jinja?      | Approaches? | -| ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------- | -| [**Hello World**](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hello-world/solutions?passed_head_tests=true) | NONE | `basics` | | ✅ | ❌ | -| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | [acronym](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | ✅ | ❌ | -| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | ✅ | ❌ | -| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | ✅ | ❌ | -| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | [allergies](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | ✅ | ❌ | -| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | ✅ | ❌ | -| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✅ | ❌ | -| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✅ | ❌ | -| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | ✅ | ❌ | -| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | ❌ | ❌ | -| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | ✅ | ❌ | -| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | [binary-search](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | ✅ | ❌ | -| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | [bob](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | ✅ | ✅ | -| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✅ | ❌ | -| [Bottle Song](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bottle-song/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json/#LC1012) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC1012) | | ✅ | ❌ | -| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | ✅ | ❌ | -| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/change/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | ✅ | ❌ | -| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | ✅ | ❌ | -| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | [clock](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | ✅ | ❌ | -| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | ✅ | ❌ | -| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | ✅ | ❌ | -| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | ✅ | ❌ | -| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | ✅ | ❌ | -| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | | ✅ | ❌ | -| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | ✅ | ❌ | -| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | ✅ | ❌ | -| [Diffie Hellman](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diffie-hellman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1956) | NONE | | ✅ | ❌ | -| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | ✅ | ❌ | -| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | ✅ | ❌ | -| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | ❌ | ❌ | -| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✅ | ❌ | -| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | ✅ | ❌ | -| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | ✅ | ❌ | -| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | ✅ | ❌ | -| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | | NONE | | ✅ | ❌ | -| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | ✅ | ❌ | -| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | [grade-school](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | ✅ | ❌ | -| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | ✅ | ✅ | -| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | ✅ | ❌ | -| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | [hamming](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | ✅ | ❌ | -| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | ❌ | ❌ | -| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 🔹 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | [high-scores](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | ✅ | ❌ | -| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/house/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✅ | ❌ | -| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✅ | ❌ | -| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | [isogram](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | ✅ | ✅ | -| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | [kindergarten-garden](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | ✅ | ❌ | -| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | ✅ | ❌ | -| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✅ | ❌ | -| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | [leap](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | ✅ | ✅ | -| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1589) | | ❌ | ❌ | -| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | ❌ | ❌ | -| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | ✅ | ❌ | -| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | [luhn](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | ✅ | ❌ | -| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L401) | [markdown](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | ✅ | ❌ | -| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | [matching-brackets](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | ✅ | ❌ | -| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | [matrix](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | ✅ | ❌ | -| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✅ | ❌ | -| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | ✅ | ❌ | -| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | ✅ | ❌ | -| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | ✅ | ❌ | -| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | ❌ | ❌ | -| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | ✅ | ❌ | -| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | ✅ | ✅ | -| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✅ | ❌ | -| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | ✅ | ❌ | -| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | ✅ | ✅ | -| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | ✅ | ❌ | -| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | ✅ | ❌ | -| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | ✅ | ❌ | -| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✅ | ❌ | -| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC661) | [config.json](https://github.com/exercism/python/blob/main/config.json#L661) | | ✅ | ❌ | -| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | ✅ | ❌ | -| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | ✅ | ❌ | -| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | ✅ | ❌ | -| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | [raindrops](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | ✅ | ❌ | -| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | ✅ | ❌ | -| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/react/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | ❌ | ❌ | -| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | ✅ | ❌ | -| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/main/config.json#LC631) | [config.json](https://github.com/exercism/python/blob/main/config.json#L631) | | ✅ | ❌ | -| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✅ | ❌ | -| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | ✅ | ❌ | -| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | ✅ | ❌ | -| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | [reverse-string](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | ✅ | ❌ | -| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | ✅ | ✅ | -| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | ❌ | ❌ | -| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | ✅ | ❌ | -| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | ✅ | ❌ | -| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | ✅ | ❌ | -| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✅ | ❌ | -| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✅ | ❌ | -| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | ✅ | ❌ | -| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/say/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✅ | ❌ | -| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | ✅ | ❌ | -| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | [scrabble-score](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | ✅ | ❌ | -| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✅ | ❌ | -| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/series/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✅ | ❌ | -| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | ✅ | ❌ | -| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | ✅ | ❌ | -| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | ✅ | ❌ | -| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | | ❌ | ❌ | -| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✅ | ❌ | -| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✅ | ❌ | -| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | ✅ | ❌ | -| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | [sum-of-multiples](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | ✅ | ❌ | -| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | ✅ | ❌ | -| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | ✅ | ❌ | -| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | ❌ | ❌ | -| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✅ | ❌ | -| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [twelve-days](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | ✅ | ❌ | -| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | ✅ | ❌ | -| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | [two-fer](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | ✅ | ❌ | -| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | ✅ | ❌ | -| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [word-count](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | ✅ | ❌ | -| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | ✅ | ❌ | -| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | ✅ | ✅ | -| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | ✅ | ❌ | -| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | ✅ | ❌ | -| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 🔹🔹🔹🔹🔹🔹 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [config.json](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | ✅ | ❌ | +| Exercise | Difficulty | Solutions | Prereqs | Practices | Hints? | Approaches? | Mentor Notes | Appends? | Jinja? | +|-------------------------------------------------------------------------------------------------------------------------------------------- |:----------: |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------- |-------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------ |---------------------------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------- | +| [**Hello World**](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.docs/instructions.md) | 1 | NA | NONE | NONE | NA | NA | NA | NA | NA | +| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/template.j2) | +| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/template.j2) | +| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/template.j2) | +| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/template.j2) | +| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/template.j2) | +| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/template.j2) | +| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/armstrong-numbers/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/template.j2) | +| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/template.j2) | +| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/template.j2) | +| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/template.j2) | +| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/template.j2) | +| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/template.j2) | +| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/template.j2) | +| [Bottle Song](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bottle-song/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json/#LC1012) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC1012) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/template.j2) | +| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/template.j2) | +| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/change/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/template.j2) | +| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/template.j2) | +| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/template.j2) | +| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/template.j2) | +| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/template.j2) | +| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/template.j2) | +| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/template.j2) | +| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/darts/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/template.j2) | +| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/template.j2) | +| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/template.j2) | +| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/dnd-character/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/template.j2) | +| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/template.j2) | +| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.append.md) | | +| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/template.j2) | +| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/template.j2) | +| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/template.j2) | +| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/template.j2) | +| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L450) | NONE | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/gigasecond/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/template.j2) | +| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/template.j2) | +| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/template.j2) | +| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grains/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/template.j2) | +| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/template.j2) | +| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/template.j2) | +| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.append.md) | | +| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/template.j2) | +| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/house/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/template.j2) | +| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isbn-verifier/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/template.j2) | +| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/template.j2) | +| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/killer-sudodu-helper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1385) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1384) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/template.j2) | +| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/template.j2) | +| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/template.j2) | +| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/template.j2) | +| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/template.j2) | +| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/template.j2) | +| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/template.j2) | +| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/template.j2) | +| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/template.j2) | +| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1418) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1417) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/template.j2) | +| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/template.j2) | +| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/template.j2) | +| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/template.j2) | +| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/template.j2) | +| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/template.j2) | +| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/template.j2) | +| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | | | | | +| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/palindrome-products/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/template.j2) | +| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/pangram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/template.j2) | +| [Pascals Triangle](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pascals-triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1300) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1299) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/hints.md) | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/template.j2) | +| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/perfect-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/template.j2) | +| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/phone-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/template.j2) | +| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/template.j2) | +| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/template.j2) | +| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 9 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/template.j2) | +| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/template.j2) | +| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/template.j2) | +| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC661) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L661) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/template.j2) | +| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/template.j2) | +| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/template.j2) | +| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/template.j2) | +| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/template.j2) | +| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/template.j2) | +| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/react/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | | | | | +| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/template.j2) | +| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC631) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L631) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/template.j2) | +| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/template.j2) | +| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/template.j2) | +| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 8 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/template.j2) | +| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/template.j2) | +| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/rna-transcription/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/template.j2) | +| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/robot-name/) | | | +| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/template.j2) | +| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/template.j2) | +| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/template.j2) | +| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/template.j2) | +| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/saddle-points/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/template.j2) | +| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/template.j2) | +| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/say/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/template.j2) | +| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/template.j2) | +| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/template.j2) | +| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/template.j2) | +| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/series/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/series/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/template.j2) | +| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/template.j2) | +| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/template.j2) | +| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/template.j2) | +| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/hints.md) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.append.md) | | +| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/space-age/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/template.j2) | +| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/template.j2) | +| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/square-root/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L609) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L608) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/template.j2) | +| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/template.j2) | +| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/template.j2) | +| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/template.j2) | +| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/template.j2) | +| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.append.md) | | +| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/triangle/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/template.j2) | +| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/hints.md) | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/template.j2) | +| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/template.j2) | +| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/template.j2) | +| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/template.j2) | +| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/template.j2) | +| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/template.j2) | +| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/template.j2) | +| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/template.j2) | +| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/template.j2) | +| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/template.j2) |
@@ -138,17 +140,17 @@ | Exercise | Difficulty | | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| [Accumulate](https://github.com/exercism/python/blob/main/exercises/practice/accumulate/.docs/instructions.md) | 🔹🔹 | -| [Beer Song](https://github.com/exercism/python/blob/main/exercises/practice/beer-song/.docs/instructions.md) | 🔹🔹🔹 | -| [Binary](https://github.com/exercism/python/blob/main/exercises/practice/binary/.docs/instructions.md) | 🔹🔹🔹 | -| [Error Handling](https://github.com/exercism/python/blob/main/exercises/practice/error-handling/.docs/instructions.md) | 🔹🔹🔹 | -| [Hexadecimal](https://github.com/exercism/python/blob/main/exercises/practice/hexadecimal/.docs/instructions.md) | 🔹🔹🔹 | -| [Nucleotide Count](https://github.com/exercism/python/blob/main/exercises/practice/nucleotide-count/.docs/instructions.md) | 🔹🔹 | -| [Parallel Letter Frequency](https://github.com/exercism/python/blob/main/exercises/practice/parallel-letter-frequency/.docs/instructions.md) | 🔹🔹🔹 | -| [Pascal's Triangle](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.md) | 🔹🔹🔹 | -| [Point Mutations](https://github.com/exercism/python/blob/main/exercises/practice/point-mutations/.docs/instructions.md) | 🔹🔹🔹 | -| [Trinary](https://github.com/exercism/python/blob/main/exercises/practice/trinary/.docs/instructions.md) | 🔹🔹🔹🔹 | -| [Custom Set](https://github.com/exercism/python/blob/main/exercises/practice/custom-set/.docs/instructions.md) | 🔹🔹🔹🔹🔹 | +| [Accumulate](https://github.com/exercism/python/blob/main/exercises/practice/accumulate/.docs/instructions.md) | 2 | +| [Beer Song](https://github.com/exercism/python/blob/main/exercises/practice/beer-song/.docs/instructions.md) | 3 | +| [Binary](https://github.com/exercism/python/blob/main/exercises/practice/binary/.docs/instructions.md) |3 | +| [Diffie-Hellman](https://github.com/exercism/python/blob/main/exercises/practice/diffie-hellman/.docs/instructions.md) |3 | +| [Error Handling](https://github.com/exercism/python/blob/main/exercises/practice/error-handling/.docs/instructions.md) | 3 | +| [Hexadecimal](https://github.com/exercism/python/blob/main/exercises/practice/hexadecimal/.docs/instructions.md) | 3 | +| [Nucleotide Count](https://github.com/exercism/python/blob/main/exercises/practice/nucleotide-count/.docs/instructions.md) | 2 | +| [Parallel Letter Frequency](https://github.com/exercism/python/blob/main/exercises/practice/parallel-letter-frequency/.docs/instructions.md) | 3 | +| [Point Mutations](https://github.com/exercism/python/blob/main/exercises/practice/point-mutations/.docs/instructions.md) |3 | +| [Trinary](https://github.com/exercism/python/blob/main/exercises/practice/trinary/.docs/instructions.md) | 4 | +| [Custom Set](https://github.com/exercism/python/blob/main/exercises/practice/custom-set/.docs/instructions.md) | 5 |
@@ -198,99 +200,125 @@
-| Status | Concept | About&Intro | Exercise | Design Doc or Issue | Stub
Docstring Level | -| :--------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | :---------------------- | -| | [basics](https://github.com/exercism/python/blob/main/concepts/basics) | | [Guidos Gorgeous Lasagna](https://github.com/exercism/python/tree/main/tree/main/exercises/concept/guidos-gorgeous-lasagna) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/guidos-gorgeous-lasagna/.meta) | Full | -| | [bools](https://github.com/exercism/python/blob/main/concepts/bools) | | [Ghost Gobble Arcade Game](https://github.com/exercism/python/tree/main/tree/main/exercises/concept/ghost-gobble-arcade-game) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/ghost-gobble-arcade-game/.meta) | Full | -| | [numbers](https://github.com/exercism/python/blob/main/concepts/numbers) | | [Currency Exchange](https://github.com/exercism/python/tree/main/exercises/concept/currency-exchange) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/currency-exchange/.meta) | Full | -| | [complex-numbers](https://github.com/exercism/python/blob/main/concepts/complex-numbers) | | ~ | [#2208](https://github.com/exercism/v3/issues/2208) | TBD | -| | [conditionals](https://github.com/exercism/python/blob/main/concepts/conditionals) | | [Meltdown Mitigation ](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation/.meta) | Full | -| | [comparisons](https://github.com/exercism/python/blob/main/concepts/comparisons) | | [Black Jack](https://github.com/exercism/python/tree/main/exercises/concept/black-jack) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/black-jack/.meta) | Full | -| | [strings](https://github.com/exercism/python/blob/main/concepts/strings) | | [Litte Sister's Vocab](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab/.meta) | Full | -| | [string-methods](https://github.com/exercism/python/blob/main/concepts/string-methods) | | [Litte Sister's Essay](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay/.meta) | Full | -| | [string-formatting](https://github.com/exercism/python/blob/main/concepts/string-formatting) | | [Pretty Leaflet ](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet/.meta) | Full | -| | [lists](https://github.com/exercism/python/blob/main/concepts/lists) | | [Card Games](https://github.com/exercism/python/tree/main/exercises/concept/card-games) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/card-games/.meta) | Full | -| | [list-methods](https://github.com/exercism/python/blob/main/concepts/list-methods) | | [Chaitanas Colossal Coaster](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster/.meta) | Full | -| | [loops](https://github.com/exercism/python/blob/main/concepts/loops) | | [Making the Grade](https://github.com/exercism/python/tree/main/exercises/concept/making-the-grade) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/making-the-grade/.meta) | Full | -| | [tuples](https://github.com/exercism/python/blob/main/concepts/tuples) | | [Tisbury Treasure Hunt](https://github.com/exercism/python/tree/main/exercises/concept/tisbury-treasure-hunt) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/tisbury-treasure-hunt/.meta) | Full | -| | [sequences](https://github.com/exercism/python/blob/main/concepts/sequences) | | ~ | [#2290](https://github.com/exercism/python/issues/2290) | TBD | -| | [dicts](https://github.com/exercism/python/blob/main/concepts/dicts) | | [Inventory Management](https://github.com/exercism/python/tree/main/exercises/concept/inventory-management) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/inventory-management) | Full | -| | [dict-methods](https://github.com/exercism/python/blob/main/concepts/dict-methods) | | ~ | [#2348](https://github.com/exercism/python/issues/2348) | | -| | [sets](https://github.com/exercism/python/blob/main/concepts/sets) | | [Cater Waiter ](https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter/.meta) | Full | -| | [list-comprehensions](https://github.com/exercism/python/blob/main/concepts/list-comprehensions) | | ~ | [#2295](https://github.com/exercism/python/issues/2295) | | -| | [other-comprehensions](https://github.com/exercism/python/blob/main/concepts/other-comprehensions) | | ~ | [#2294](https://github.com/exercism/python/issues/2294) | | -| | [classes](https://github.com/exercism/python/blob/main/concepts/classes) | | [Ellen's Alien Game](https://github.com/exercism/python/tree/main/exercises/concept/ellens-alien-game) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/ellens-alien-game/.meta) | Minimal | -| | [generators](https://github.com/exercism/python/blob/main/concepts/generators) | | Plane Tickets | [PR#2729](https://github.com/exercism/python/pull/2729)/[#2293](https://github.com/exercism/python/issues/2293) | Minimal | -| | [generator-expressions](https://github.com/exercism/python/blob/main/concepts/generator-expressions) | | ~ | [#2292](https://github.com/exercism/python/issues/2292) | | -| | [iterators](https://github.com/exercism/python/blob/main/concepts/iterators) | | ~ | [#2367](https://github.com/exercism/python/issues/2367) | TBD | -| | [functions](https://github.com/exercism/python/blob/main/concepts/functions) | | ~ | [#2353](https://github.com/exercism/python/issues/2353) | | -| | [unpacking-and-multiple-assignment](https://github.com/exercism/python/blob/main/concepts/unpacking-and-multiple-assignment) | | ~ | [#2360](https://github.com/exercism/python/issues/2360) | | -| | [raising-and-handling-errors](https://github.com/exercism/python/blob/main/concepts/raising-and-handling-errors) | | ~ | TBD | | -| | [itertools](https://github.com/exercism/python/blob/main/concepts/itertools) | | ~ | [#2368](https://github.com/exercism/python/issues/2368) | | -| | [with-statement](https://github.com/exercism/python/blob/main/concepts/with-statement) | | ~ | [#2369](https://github.com/exercism/python/issues/2369) | | -| | [enums](https://github.com/exercism/python/blob/main/concepts/enums) | | [Log Levels](https://github.com/exercism/python/tree/main/exercises/concept/log-levels) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/log-levels) | Minimal | -| | [none](https://github.com/exercism/python/blob/main/concepts/none) | | [Restaurant Rozalynn](https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn/.meta) | Minimal | -| | [decorators](https://github.com/exercism/python/blob/main/concepts/decorators) | | ~ | [#2356](https://github.com/exercism/python/issues/2356) | | -| | [rich-comparisons](https://github.com/exercism/python/blob/main/concepts/rich-comparisons) | | ~ | [#2287](https://github.com/exercism/python/issues/2287) | | -| | [function-arguments](https://github.com/exercism/python/blob/main/concepts/function-arguments) | | ~ | [#2354](https://github.com/exercism/python/issues/2354) | | -| | [class-customization](https://github.com/exercism/python/blob/main/concepts/class-customization) | | ~ | [#2350](https://github.com/exercism/python/issues/2350) | | -| | [class-inheritance](https://github.com/exercism/python/blob/main/concepts/class-inheritance) | | ~ | [#2351](https://github.com/exercism/python/issues/2351) | | -| | [user-defined-errors](https://github.com/exercism/python/blob/main/concepts/user-defined-errors) | | ~ | TBD | | -| | [context-manager-customization](https://github.com/exercism/python/blob/main/concepts/context-manager-customization) | | ~ | [#2370](https://github.com/exercism/python/issues/2370) | | -| | [higher-order-functions](https://github.com/exercism/python/blob/main/concepts/higher-order-functions) | | ~ | [#2355](https://github.com/exercism/python/issues/2355) | | -| | [functional-tools](https://github.com/exercism/python/blob/main/concepts/functional-tools) | | ~ | [#2359](https://github.com/exercism/python/issues/2359) | | -| | [functools](https://github.com/exercism/python/blob/main/concepts/functools) | | ~ | [#2366](https://github.com/exercism/python/issues/2366) | | -| | [anonymous-functions](https://github.com/exercism/python/blob/main/concepts) | | ~ | [#2357](https://github.com/exercism/python/issues/2357) | | -| | [descriptors](https://github.com/exercism/python/blob/main/concepts/descriptors) | | ~ | [#2365](https://github.com/exercism/python/issues/2365) | | -| | [aliasing](https://github.com/exercism/python/blob/main/concepts) | | ~ | TBD | | -| | [binary data](https://github.com/exercism/python/blob/main/concepts/binary-data) | | ~ | TBD | | -| | [bitflags](https://github.com/exercism/python/blob/main/concepts/bitflags) | | ~ | TBD | | -| | [bitwise-operators](https://github.com/exercism/python/blob/main/concepts/bitwise-operators) | | ~ | TBD | | -| | [bytes](https://github.com/exercism/python/blob/main/concepts/bytes) | | ~ | TBD | | -| | [class-composition](https://github.com/exercism/python/blob/main/concepts/class-composition) | | ~ | [#2352](https://github.com/exercism/python/issues/2352) | | -| | [class-interfaces](https://github.com/exercism/python/blob/main/concepts/class-interfaces) | | ~ | TBD | | -| | [collections](https://github.com/exercism/python/blob/main/concepts/collections) | | ~ | TBD | | -| | [dataclasses-and-namedtuples](https://github.com/exercism/python/blob/main/concepts/dataclasses-and-namedtuples) | | ~ | [#2361](https://github.com/exercism/python/issues/2361) | | -| | [import](https://github.com/exercism/python/blob/main/concepts/import) | | ~ | ON HOLD | | -| | [memoryview](https://github.com/exercism/python/blob/main/concepts/memoryview) | | ~ | TBD | | -| | [operator-overloading](https://github.com/exercism/python/blob/main/concepts/operator-overloading) | | ~ | TBD | | -| | [regular-expressions](https://github.com/exercism/python/blob/main/concepts/regular-expressions) | | ~ | TBD | | -| | [string-methods-splitting](https://github.com/exercism/python/blob/main/concepts/string-methods-splitting) | | ~ | TBD | | -| | [testing](https://github.com/exercism/python/blob/main/concepts/testing) | | ~ | TBD | | -| | [text-processing](https://github.com/exercism/python/blob/main/concepts/text-processing) | | ~ | TBD | | -| | [type-hinting](https://github.com/exercism/python/blob/main/concepts/type-hinting) | | ~ | TBD | | -| | [unicode-regular-expressions](https://github.com/exercism/python/blob/main/concepts/unicode-regular-expressions) | | ~ | TBD | | -| | [walrus-operator](https://github.com/exercism/python/blob/main/concepts/walrus-operator) | | ~ | TBD | | +## Implemented & Planned Concept Exercises + +

= live on exercism.org        + = drafted but not live

+

= planned or in progress    + = future

+ +
+ +| Status | Concept | About&Intro | Exercise | Design Doc or Issue | Stub
Docstring Level | +| :--------------------------------------------: | :----------------------------------------------------------- | :--------------------------------------------: | :----------------------------------------------------------- | :----------------------------------------------------------- | :---------------------- | +| | [basics](https://github.com/exercism/python/blob/main/concepts/basics) | | [Guidos Gorgeous Lasagna](https://github.com/exercism/python/tree/main/tree/main/exercises/concept/guidos-gorgeous-lasagna) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/guidos-gorgeous-lasagna/.meta) | Full | +| | [bools](https://github.com/exercism/python/blob/main/concepts/bools) | | [Ghost Gobble Arcade Game](https://github.com/exercism/python/tree/main/tree/main/exercises/concept/ghost-gobble-arcade-game) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/ghost-gobble-arcade-game/.meta) | Full | +| | [numbers](https://github.com/exercism/python/blob/main/concepts/numbers) | | [Currency Exchange](https://github.com/exercism/python/tree/main/exercises/concept/currency-exchange) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/currency-exchange/.meta) | Full | +| | [complex-numbers](https://github.com/exercism/python/blob/main/concepts/complex-numbers) | | ~ | [#2208](https://github.com/exercism/v3/issues/2208) | TBD | +| | [conditionals](https://github.com/exercism/python/blob/main/concepts/conditionals) | | [Meltdown Mitigation ](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation/.meta) | Full | +| | [comparisons](https://github.com/exercism/python/blob/main/concepts/comparisons) | | [Black Jack](https://github.com/exercism/python/tree/main/exercises/concept/black-jack) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/black-jack/.meta) | Full | +| | [strings](https://github.com/exercism/python/blob/main/concepts/strings) | | [Litte Sister's Vocab](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab/.meta) | Full | +| | [string-methods](https://github.com/exercism/python/blob/main/concepts/string-methods) | | [Litte Sister's Essay](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay/.meta) | Full | +| | [string-formatting](https://github.com/exercism/python/blob/main/concepts/string-formatting) | | TBD (_rewrite_) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet/.meta) | Full | +| | [lists](https://github.com/exercism/python/blob/main/concepts/lists) | | [Card Games](https://github.com/exercism/python/tree/main/exercises/concept/card-games) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/card-games/.meta) | Full | +| | [list-methods](https://github.com/exercism/python/blob/main/concepts/list-methods) | | [Chaitanas Colossal Coaster](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster/.meta) | Full | +| | [loops](https://github.com/exercism/python/blob/main/concepts/loops) | | [Making the Grade](https://github.com/exercism/python/tree/main/exercises/concept/making-the-grade) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/making-the-grade/.meta) | Full | +| | [tuples](https://github.com/exercism/python/blob/main/concepts/tuples) | | [Tisbury Treasure Hunt](https://github.com/exercism/python/tree/main/exercises/concept/tisbury-treasure-hunt) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/tisbury-treasure-hunt/.meta) | Full | +| | [sequences](https://github.com/exercism/python/blob/main/concepts/sequences) | | [Thalias Tram Troubles]() | [#2290](https://github.com/exercism/python/issues/2290) | TBD | +| | [dicts](https://github.com/exercism/python/blob/main/concepts/dicts) | | [Inventory Management](https://github.com/exercism/python/tree/main/exercises/concept/inventory-management) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/inventory-management) | Full | +| | [dict-methods](https://github.com/exercism/python/blob/main/concepts/dict-methods) | | [Mecha Munch Management](https://github.com/exercism/python/tree/main/exercises/concept/mecha-munch-management) | [.meta folder](https://github.com/exercism/python/tree/main/exercises/concept/mecha-munch-management/.meta) | Full | +| | [sets](https://github.com/exercism/python/blob/main/concepts/sets) | | [Cater Waiter ](https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter/.meta) | Full | +| | [list-comprehensions](https://github.com/exercism/python/blob/main/concepts/list-comprehensions) | | ~ | [#2295](https://github.com/exercism/python/issues/2295) | | +| | [other-comprehensions](https://github.com/exercism/python/blob/main/concepts/other-comprehensions) | | ~ | [#2294](https://github.com/exercism/python/issues/2294) | | +| | [classes](https://github.com/exercism/python/blob/main/concepts/classes) | | [Ellen's Alien Game](https://github.com/exercism/python/tree/main/exercises/concept/ellens-alien-game) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/ellens-alien-game/.meta) | Minimal | +| | [generators](https://github.com/exercism/python/blob/main/concepts/generators) | | [Plane Tickets](https://github.com/exercism/python/tree/main/exercises/concept/plane-tickets) | [.meta folder (WIP)](https://github.com/exercism/python/tree/main/exercises/concept/plane-tickets/.meta) | Minimal | +| | [generator-expressions](https://github.com/exercism/python/blob/main/concepts/generator-expressions) | | ~ | [#2292](https://github.com/exercism/python/issues/2292) | | +| | [iterators](https://github.com/exercism/python/blob/main/concepts/iterators) | | ~ | [#2367](https://github.com/exercism/python/issues/2367) | TBD | +| | [functions](https://github.com/exercism/python/blob/main/concepts/functions) | | ~ | [#2353](https://github.com/exercism/python/issues/2353) | | +| | [unpacking-and-multiple-assignment](https://github.com/exercism/python/blob/main/concepts/unpacking-and-multiple-assignment) | | [Locomotive Engineer](https://exercism.org/tracks/python/exercises/locomotive-engineer) | [.meta folder](https://github.com/exercism/python/tree/main/exercises/concept/locomotive-engineer/.meta) | Full | +| | [raising-and-handling-errors](https://github.com/exercism/python/blob/main/concepts/raising-and-handling-errors) | | ~ | TB | | +| | [itertools](https://github.com/exercism/python/blob/main/concepts/itertools) | | Ice Cream Stand | [#2368](https://github.com/exercism/python/issues/2368) & [PR #3288](https://github.com/exercism/python/pull/3288) | Minimal | +| | [with-statement](https://github.com/exercism/python/blob/main/concepts/with-statement) | | ~ | [#2369](https://github.com/exercism/python/issues/2369) | | +| | [enums](https://github.com/exercism/python/blob/main/concepts/enums) | | [Log Levels](https://github.com/exercism/python/tree/main/exercises/concept/log-levels) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/log-levels) | Minimal | +| | [none](https://github.com/exercism/python/blob/main/concepts/none) | | [Restaurant Rozalynn](https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn/.meta) | Minimal | +| | [decorators](https://github.com/exercism/python/blob/main/concepts/decorators) | | ~ | [#2356](https://github.com/exercism/python/issues/2356) | | +| | [rich-comparisons](https://github.com/exercism/python/blob/main/concepts/rich-comparisons) | | ~ | [#2287](https://github.com/exercism/python/issues/2287) | | +| | [function-arguments](https://github.com/exercism/python/blob/main/concepts/function-arguments) | | ~ | [#2354](https://github.com/exercism/python/issues/2354) | | +| | [class-customization](https://github.com/exercism/python/blob/main/concepts/class-customization) | | ~ | [#2350](https://github.com/exercism/python/issues/2350) | | +| | [class-inheritance](https://github.com/exercism/python/blob/main/concepts/class-inheritance) | | ~ | [#2351](https://github.com/exercism/python/issues/2351) | | +| | [user-defined-errors](https://github.com/exercism/python/blob/main/concepts/user-defined-errors) | | ~ | TBD | | +| | [context-manager-customization](https://github.com/exercism/python/blob/main/concepts/context-manager-customization) | | ~ | [#2370](https://github.com/exercism/python/issues/2370) | | +| | [higher-order-functions](https://github.com/exercism/python/blob/main/concepts/higher-order-functions) | | ~ | [#2355](https://github.com/exercism/python/issues/2355) | | +| | [functional-tools](https://github.com/exercism/python/blob/main/concepts/functional-tools) | | ~ | [#2359](https://github.com/exercism/python/issues/2359) | | +| | [functools](https://github.com/exercism/python/blob/main/concepts/functools) | | ~ | [#2366](https://github.com/exercism/python/issues/2366) | | +| | [anonymous-functions](https://github.com/exercism/python/blob/main/concepts) | | ~ | [#2357](https://github.com/exercism/python/issues/2357) | | +| | [descriptors](https://github.com/exercism/python/blob/main/concepts/descriptors) | | ~ | [#2365](https://github.com/exercism/python/issues/2365) | | +| | [aliasing](https://github.com/exercism/python/blob/main/concepts) | | ~ | TBD | | +| | [binary data](https://github.com/exercism/python/blob/main/concepts/binary-data) | | ~ | TBD | | +| | [bitflags](https://github.com/exercism/python/blob/main/concepts/bitflags) | | ~ | TBD | | +| | [bitwise-operators](https://github.com/exercism/python/blob/main/concepts/bitwise-operators) | | ~ | TBD | | +| | [bytes](https://github.com/exercism/python/blob/main/concepts/bytes) | | ~ | TBD | | +| | [class-composition](https://github.com/exercism/python/blob/main/concepts/class-composition) | | ~ | [#2352](https://github.com/exercism/python/issues/2352) | | +| | [class-interfaces](https://github.com/exercism/python/blob/main/concepts/class-interfaces) | | ~ | TBD | | +| | [collections](https://github.com/exercism/python/blob/main/concepts/collections) | | ~ | TBD | | +| | [dataclasses](https://github.com/exercism/python/blob/main/concepts/dataclasses) | | ~ | [#2361](https://github.com/exercism/python/issues/2361) | | +| | [import](https://github.com/exercism/python/blob/main/concepts/import) | | ~ | ON HOLD | | +| | [memoryview](https://github.com/exercism/python/blob/main/concepts/memoryview) | | ~ | TBD | | +| | [operator-overloading](https://github.com/exercism/python/blob/main/concepts/operator-overloading) | | ~ | TBD | | +| | [regular-expressions](https://github.com/exercism/python/blob/main/concepts/regular-expressions) | | ~ | TBD | | +| | [string-methods-splitting](https://github.com/exercism/python/blob/main/concepts/string-methods-splitting) | | ~ | TBD | | +| | [testing](https://github.com/exercism/python/blob/main/concepts/testing) | | ~ | TBD | | +| | [text-processing](https://github.com/exercism/python/blob/main/concepts/text-processing) | | ~ | TBD | | +| | [type-hinting](https://github.com/exercism/python/blob/main/concepts/type-hinting) | | ~ | TBD | | +| | [unicode-regular-expressions](https://github.com/exercism/python/blob/main/concepts/unicode-regular-expressions) | | ~ | TBD | | +| | [walrus-operator](https://github.com/exercism/python/blob/main/concepts/walrus-operator) | | ~ | TBD | |

+ ## Concept Exercise Tree ```mermaid flowchart TD %%{init: {"theme": "base", "themeVariables": { "fontFamily": "Victor Mono, San Francisco, Roboto", "fontSize" : "18px", "primaryColor": "#D9D7FF", "nodeBorder": "#9F80DF", "lineColor": "#AFAC6A"}}}%% +classDef TBD-F fill:#C4D7FF,stroke:#97ACF5,stroke-width:4px,stroke-dasharray: 5 5, color: #5055C4; classDef TBD fill:#D9D7FF,stroke:#9F80DF,stroke-width:4px,stroke-dasharray: 5 5, color: #734CC4; classDef Beta fill:#F5DE90,stroke:#F6B303,stroke-width:2px, color:#525952; classDef WIP fill:#DEE8BB,stroke:#AFAC6A,stroke-width:4px,stroke-dasharray: 5 5, #908D49; %%concepts & exercise names (node labels) +aliasing((TBD-F
aliasing)):::TBD-F +array((TBD-F
array)):::TBD-F Basics((Guidos Gorgeous Lasagna
Basics)):::Beta +binary-data((TBD-F
binary-data>)):::TBD-F +bitwise-operations((TBD-F
bitwise-
operations
)):::TBD-F bools((Ghost Gobble
Arcade Game

bools)):::Beta +calendar((TBD-F
calendar)):::TBD-F classes((Ellen's Alien Game
Classes)):::Beta Class-customization((TBD
Class Customization)):::TBD +Class-composition((TBD-F
Class Composition)):::TBD-F Class-inheritance((TBD
Class Inheritance)):::TBD Class-interfaces((TBD
Class Interfaces)):::TBD -conditionals((Meltdown Mitigation
Conditionals)):::Beta +collections((TBD
Collections Module)):::TBD comparisons((Black Jack
comparisons)):::Beta +conditionals((Meltdown Mitigation
Conditionals)):::Beta + context-manager-customization((TBD
Context Manager
Customization
)):::TBD +copy((copy
copy)):::TBD-F +datetime((TBD
datetime)):::TBD decorators((TBD
Decorators)):::TBD +decimal((TBD-F
decimal)):::TBD-F descriptors((TBD
Descriptors)):::TBD list-comprehensions(("TBD
List Comprehensions")):::TBD other-comprehensions((TBD
Other Comprehensions)):::TBD +dataclasses((TBD-F
dataclasses)):::TBD-F dicts((Inventory
Management
dicts)):::Beta -dict-methods((TBD
Dict-Methods)):::TBD +dict-methods((Mecha
Munch Management

Dict-Methods)):::WIP enums((Log Levels
Enums)):::WIP -functions((TBD
Functions)):::WIP +enumerate((TBD
enumerate)):::TBD +ExceptionGroup((TBD-F
ExceptionGroup)):::TBD-F +fractions((TBD-F
fractions)):::TBD-F +functions((Exercise TBD
Functions)):::WIP function-arguments((TBD
Function Arguments)):::TBD functional-tools((TBD
Functional Tools)):::TBD functools((TBD
Functools Module)):::TBD @@ -298,64 +326,99 @@ generators((Plane Tickets
Generators)):::TBD generator-expressions((TBD
Generator Expressions)):::TBD higher-order-functions((TBD
Higher Order
Functions
)):::TBD anonymous-functions((TBD
Anonymous Functions
AKA Lambdas
)):::TBD +imports((TBD-F
imports)):::TBD-F iterators((TBD
iterators)):::TBD -itertools((TBD
itertools)):::TBD +itertools((Ice Cream Stand
itertools)):::WIP lists((Card Games
lists)):::Beta list-methods((Chaitana's
Colossal Coaster

list-methods)):::Beta +logging((TBD-F
Logging)):::TBD-F loops((Making the Grade
loops)):::Beta +math((TBD-F
math)):::TBD-F +cmath((TBD-F
cmath)):::TBD-F +memoryview((TBD-F
memoryview)):::TBD-F none((Restaurant Rozalynn
none)):::WIP numbers(("Currency Exchange
(ints & floats)")):::Beta -complex-numbers(("TBD (Bowling Game??)
complex-numbers
")):::TBD +complex-numbers(("TBD
(Bowling Game??)
complex-numbers
")):::TBD +binary-octal-hexadecimal(("WIP
Binary, Octal, &
Hexadecimal
")):::WIP +operator-overloading((TBD-F
operator-
overloading
)):::TBD-F raising-and-handling-errors((TBD
Raising &
Handling Errors
)):::TBD +regular-expressions((TBD
Regular Expressions)):::TBD +unicode-regular-expressions((TBD-F
Unicode Regex)):::TBD-F +random((TBD-F
random)):::TBD-F rich-comparisons((TBD
Rich Comparisons)):::TBD -sequences((TBD
sequences)):::TBD +statistics((TBD-F
statistics)):::TBD-F +secrets((TBD-F
secrets)):::TBD-F +sequences((Thalias
Tram Troubles

sequences)):::WIP sets((Cater-Waiter
sets)):::Beta strings((Little Sister's
Vocab

strings)):::Beta -string-formatting((Pretty Leaflet
String Formatting)):::WIP +string-formatting(("TBD (rewrite)
String Formatting")):::WIP string-methods((Little Sister's
Essay

String Methods)):::Beta -tuples((Tisbury
Treasure Hunt

tuples)):::Beta -unpacking-and-multiple-assignment((TBD
Unpacking
& Multi-assignment
)):::TBD +structural-pattern-matching((TBD
Structural
Pattern Matching
)):::TBD +tuples((Tisbury Treasure Hunt
tuples)):::Beta +type-annotation(("TBD-F
Type Annotation
(type hints)
")):::TBD-F +type-aliases((TBD-F
Type Aliases)):::TBD-F +unicode-data((TBD-F
unicode data)):::TBD-F +unpacking-and-multiple-assignment((Locomotive Engineer
Unpacking &
Multi-assignment
)):::Beta user-defined-errors((TBD
User Definied Errors)):::TBD +walrus-operator((TBD-F
Walrus Operator)):::TBD-F with(("TBD
with
(Context Managers)
")):::TBD +unittest(("TBD-F
unittest
(testing)
")):::TBD-F +doctests(("TBD-F
doctests")):::TBD-F +zip(("TBD
iterating with zip()")):::TBD %%exercise prerequisites (node relations) -Basics --> strings & numbers & bools & loops -Basics --> functions -bools --> conditionals -classes ---> iterators & Class-inheritance & Class-customization -conditionals --> strings & comparisons & loops +Basics --> strings & bools +Basics --> numbers & loops +binary-data --> memoryview +bools --> conditionals & bitwise-operations +classes ---> iterators & dataclasses & Class-inheritance & Class-customization +Class-customization --> type-annotation & Class-composition +conditionals --> strings & comparisons comparisons --> loops -loops --> tuples & with -loops --> itertools & functions +datetime --> calendar +decorators --> Class-customization +loops --> regular-expressions & tuples & imports +loops --> functions & with +regular-expressions --> structural-pattern-matching & unicode-regular-expressions list-comprehensions --> other-comprehensions -Class-customization --> rich-comparisons -Class-customization --> enums & decorators +Class-customization --> unittest & enums & rich-comparisons & logging +unittest --> doctests +Class-customization --> operator-overloading Class-inheritance --> user-defined-errors & descriptors & Class-interfaces Class-inheritance ----> context-manager-customization -other-comprehensions ---> generators -dicts --> dict-methods -functions --> function-arguments & higher-order-functions & functional-tools +other-comprehensions ---> walrus-operator & generators +dicts --> dict-methods & copy +functions --> function-arguments & higher-order-functions +functions --> functional-tools function-arguments --> none functional-tools --> functools generators --> generator-expressions -higher-order-functions ---> decorators +higher-order-functions --> decorators higher-order-functions --> anonymous-functions +imports --> itertools & datetime & aliasing iterators --> generators -lists --> string-formatting & dicts & list-methods & list-comprehensions & sequences -numbers --> complex-numbers -sequences --> iterators -sets --> classes -strings --> string-methods & string-formatting & lists -strings --> raising-and-handling-errors -tuples --> sequences & sets & classes & unpacking-and-multiple-assignment +lists --> string-formatting & dicts & list-methods & list-comprehensions & array & sequences +numbers --> bitwise-operations & complex-numbers & fractions & decimal & binary-octal-hexadecimal & random & math & statistics +complex-numbers --> cmath +random --> secrets +sequences --> binary-data & iterators & enumerate +sets --> classes & collections +strings ----> string-formatting +strings --> raising-and-handling-errors & lists & string-methods & unicode-data & regular-expressions +tuples --> sequences & classes +tuples --> zip & unpacking-and-multiple-assignment +tuples ---> sets +tuples --> itertools +type-annotation --> type-aliases +user-defined-errors --> ExceptionGroup with --> context-manager-customization click Basics "https://exercism.org/tracks/python/exercises/guidos-gorgeous-lasagna" "basics" click bools "https://exercism.org/tracks/python/exercises/ghost-gobble-arcade-game" "bools" click classes "https://exercism.org/tracks/python/exercises/ellens-alien-game" "classes" -click Class-customization "https://github.com/exercism/python/tree/main/concepts/class-customization" "Class-customization" -click Class-inheritance "https://github.com/exercism/python/tree/main/concepts/class-inheritance" "Class-inheritance" +click Class-customization "https://github.com/exercism/python/issues/3094" "Class-customization" +click Class-inheritance "https://github.com/exercism/python/issues/3096" "Class-inheritance" click Class-interfaces "https://github.com/exercism/python/tree/main/concepts/class-interfaces" "Class-interfaces" click conditionals "https://exercism.org/tracks/python/exercises/meltdown-mitigation" "conditionals" click comparisons "https://exercism.org/tracks/python/exercises/black-jack" "comparisons" @@ -366,13 +429,13 @@ click list-comprehensions "https://github.com/exercism/python/issues/2295" "list click other-comprehensions "https://github.com/exercism/python/issues/2294" "other-comprehensions" click conditionals "https://exercism.org/tracks/python/exercises/meltdown-mitigation" "conditionals" click dicts "https://exercism.org/tracks/python/exercises/inventory-management" "dicts" -click dict-methods "https://github.com/exercism/python/issues/2348" "dict-methods" +click dict-methods "https://github.com/exercism/python/tree/main/exercises/concept/mecha-munch-management" "dict-methods" click enums "https://github.com/exercism/python/tree/main/exercises/concept/restaurant-rozalynn" "enums" click functions "https://github.com/exercism/python/issues/2353" "functions" click function-arguments "https://github.com/exercism/python/issues/2354" "function-arguments" click functional-tools "https://github.com/exercism/python/issues/2359" "functional-tools" click functools "https://github.com/exercism/python/issues/2366" "functools" -click generators "https://github.com/exercism/python/issues/2293" "generators" +click generators "https://github.com/exercism/python/tree/main/exercises/concept/plane-tickets" "generators" click generator-expressions "https://github.com/exercism/python/issues/2292" "generator-expressions" click higher-order-functions "https://github.com/exercism/python/issues/2355" "higher-order-functions" click anonymous-functions "https://github.com/exercism/python/issues/2357" "anonymous-functions" @@ -392,7 +455,7 @@ click strings "https://exercism.org/tracks/python/exercises/little-sisters-vocab click string-formatting "https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet" "string-formatting" click string-methods "https://exercism.org/tracks/python/exercises/little-sisters-essay" "string-methods" click tuples "https://exercism.org/tracks/python/exercises/tisbury-treasure-hunt" "tuples" -click unpacking-and-multiple-assignment "https://github.com/exercism/python/issues/2360" "unpacking-and-multiple-assignment" +click unpacking-and-multiple-assignment "https://github.com/exercism/python/tree/main/exercises/concept/locomotive-engineer" "unpacking-and-multiple-assignment" click user-defined-errors "https://github.com/exercism/python/tree/main/concepts/user-defined-errors" "user-defined-errors" click with "https://github.com/exercism/python/issues/2369" "with" ``` From a225ea98b41ab6cc332fcaee06ee4508efb0147a Mon Sep 17 00:00:00 2001 From: Srini Maiya <75990547+SriniMaiya@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:04:07 +0200 Subject: [PATCH 503/826] Typo in writing 999 billion .... 999 thousand 999 (#3476) Missing a 9 in the unit's digit. Does not match the desciption given at instructions.md of "It's fine to stop at "trillion". --- exercises/practice/say/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/say/.docs/instructions.append.md b/exercises/practice/say/.docs/instructions.append.md index aed8f06774..43365a7397 100644 --- a/exercises/practice/say/.docs/instructions.append.md +++ b/exercises/practice/say/.docs/instructions.append.md @@ -12,6 +12,6 @@ To raise a `ValueError` with a message, write the message as an argument to the # if the number is negative raise ValueError("input out of range") -# if the number is larger than 999,999,999,99 +# if the number is larger than 999,999,999,999 raise ValueError("input out of range") ``` From 053e71e2e3accca27f92d9cbd39dd5645d9ba36f Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 26 Jul 2023 19:47:54 +0200 Subject: [PATCH 504/826] simple-linked-list: use AWS hosted images (#3475) * simple-linked-list: use AWS hosted images * Apply suggestions from code review --------- Co-authored-by: BethanyG --- exercises/practice/simple-linked-list/.docs/hints.md | 2 +- .../practice/simple-linked-list/.docs/instructions.append.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/simple-linked-list/.docs/hints.md b/exercises/practice/simple-linked-list/.docs/hints.md index 4fd7f9ace0..1ecef83ea2 100644 --- a/exercises/practice/simple-linked-list/.docs/hints.md +++ b/exercises/practice/simple-linked-list/.docs/hints.md @@ -5,7 +5,7 @@ - This challenge is about creating a [_stack_][Baeldung: The Stack Data Structure] using a [singly linked list][singly linked list]. - Unlike stacks underpinned with `lists`, `collections.deque`, or `queue.LifoQueue`, we ask you create custom `Node` and `LinkedList` [`classes`][classes tutorial] to store and link elements. -![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". Node_4 has a solid arrow pointing rightward to Node_3, which reads "Node_3 - next = node_2". Node_3 has a solid arrow pointing rightward to Node_2, which reads "Node_2 - next = node_1". Node_2 has a solid arrow pointing rightward to Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://media.githubusercontent.com/media/exercism/v3-files/main/python/simple-linked-list/linked-list.svg) +![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". This pattern continues until Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/tracks/python/simple-linked-list/linked-list.svg) - [Real Python: Linked Lists][Real Python Linked Lists], [Towards Data Science: Demystifying the Linked List][towards data science demystifying the linked list], and [ADS Stack in Python][Koder Dojo Coding an ADS Stack in Python] can be helpful to review for details on implementation. - Your `LinkedList` should accept a `list` argument to its _constructor_, but should not use a `list` to store nodes or elements. diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/instructions.append.md index 8e565516c4..41db11df96 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.append.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.append.md @@ -4,12 +4,12 @@ While `stacks` and `queues` can be implemented using `lists`, `collections.deque`, `queue.LifoQueue`, and `multiprocessing.Queue`, this exercise expects a ["Last in, First Out" (`LIFO`) stack][Baeldung: The Stack Data Structure] (_interactive example [here][LIFO Stack]_) using a _custom-made_ [singly linked list][singly linked list]. -![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". Node_4 has a solid arrow pointing rightward to Node_3, which reads "Node_3 - next = node_2". Node_3 has a solid arrow pointing rightward to Node_2, which reads "Node_2 - next = node_1". Node_2 has a solid arrow pointing rightward to Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://media.githubusercontent.com/media/exercism/v3-files/main/python/simple-linked-list/linked-list.svg) +![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". This pattern continues until Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/tracks/python/simple-linked-list/linked-list.svg) This should not be confused with a [`LIFO` stack using a dynamic array or list][LIFO Stack Array], which may use a `list` underneath. Dynamic array based `stacks` have a different `head` position and different time complexity (Big-O) and memory footprint. -![Diagram representing a stack implemented with an array/dynamic array. A box with a dashed border named New_Node is to the far right-hand side, with two dotted arrow lines pointing left-ward. New_Node reads "(becomes head) - New_Node". The top dotted arrow line is labeled "append" and points to Node_6, above and to the left. Node_6 reads "(current) head - Node_6". The bottom dotted arrow line is labeled "pop" and points to a box with a dotted outline that reads "gets removed on pop()". Node_6 has a solid arrow that points leftward to Node_5. Node_5 has a solid arrow pointing leftward to Node_4. Node_4 has a solid arrow pointing leftward to Node_3. Node_3 has a solid arrow pointing rightward to Node_2. Node_2 has a solid arrow pointing rightward to Node_1, which reads "(current) tail - Node_1".](https://media.githubusercontent.com/media/exercism/v3-files/main/python/simple-linked-list/linked_list_array.svg) +![Diagram representing a stack implemented with an array/dynamic array. A box with a dashed border named New_Node is to the far right-hand side, with two dotted arrow lines pointing left-ward. New_Node reads "(becomes head) - New_Node". The top dotted arrow line is labeled "append" and points to Node_6, above and to the left. Node_6 reads "(current) head - Node_6". The bottom dotted arrow line is labeled "pop" and points to a box with a dotted outline that reads "gets removed on pop()". Node_6 has a solid arrow that points leftward to Node_5. Node_5 has a solid arrow pointing leftward to Node_4. This pattern continues until Node_1, which reads "(current) tail - Node_1".](https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/tracks/python/simple-linked-list/linked_list_array.svg) See these two Stack Overflow questions for some considerations: [Array-Based vs List-Based Stacks and Queues][Stack Overflow: Array-Based vs List-Based Stacks and Queues] and [Differences between Array Stack, Linked Stack, and Stack][Stack Overflow: What is the difference between Array Stack, Linked Stack, and Stack]. From 6ac3e06d89dbf0ddf3947d772d143214141d4024 Mon Sep 17 00:00:00 2001 From: Andrew Lawendy <22712035+AndrewLawendy@users.noreply.github.com> Date: Sat, 29 Jul 2023 16:23:51 +0300 Subject: [PATCH 505/826] [Gigasecond]: Large Number Formatting Instruction Append and Hints File (#3473) * docs(gigasecond): init dig deeper * docs(gigasecond): add scientific notation reference * docs: :memo: add config.json * docs(config): :fire: remove approaches * docs(approaches): :fire: remove info from approaches * docs(instructions): :sparkles: add info in append * docs(hints): :sparkles: add info * docs: :bug: fix underscores notation example * Reformatted for display [no important files changed] --------- Co-authored-by: BethanyG --- exercises/practice/gigasecond/.docs/hints.md | 14 ++++++++++++-- .../gigasecond/.docs/instructions.append.md | 16 ++++++++++++++-- exercises/practice/gigasecond/.meta/config.json | 3 ++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/exercises/practice/gigasecond/.docs/hints.md b/exercises/practice/gigasecond/.docs/hints.md index 0375a753af..d4891ef8eb 100644 --- a/exercises/practice/gigasecond/.docs/hints.md +++ b/exercises/practice/gigasecond/.docs/hints.md @@ -1,6 +1,16 @@ # Hints -## General -- Your code should parse a datetime object, add a gigasecond's worth of time to it, and then return the result as a datetime object. +## General + +- Your function should parse the passed-in [datetime object][dtatetime], add a gigasecond's worth of time to it, and then return the result. - If you're having trouble, remember to take a look at the provided test cases under the Tests tab. These will help you figure out what the expected inputs and outputs of your function(s) should be. + +- Most of the time, code is read rather than written, and a big number can be a challenge to read. Here are a couple of approaches to making big numbers in your code more readable: + + - Using underscores (`_`) in numeric literals can help offset thousands, hundred-thousands, millions, etc. (_**ie:** `1_000_000` or `10_100_201_330` is far more readable than `1000000` or `10100201330`._) See [PEP-0515][underscores_notation] for more information. + + - Scientific notation can be more compact and easier to scan when there are very large numbers (_**ie:** `1e6`, 1 is multiplied by 10 raised to the power of 6, which equals `1000000`_). For more information, see this reference on [scientific notation][scientific_notation]. + +[scientific_notation]: https://python-reference.readthedocs.io/en/latest/docs/float/scientific.html +[underscores_notation]: https://peps.python.org/pep-0515/#:~:text=The%20syntax%20would%20be%20the,width%20of%2010%20with%20*%20separator. diff --git a/exercises/practice/gigasecond/.docs/instructions.append.md b/exercises/practice/gigasecond/.docs/instructions.append.md index 832fec734c..ccb6f2d05b 100644 --- a/exercises/practice/gigasecond/.docs/instructions.append.md +++ b/exercises/practice/gigasecond/.docs/instructions.append.md @@ -1,5 +1,15 @@ # Instructions append +## Reading and Writing Long Numbers + +Code is more often _read_ than it is written, and reading a big/long number within other text can be a challenge. +Here are two approaches to making numbers more readable: + +1. Using underscores in Numeric Literals. `1_000_000` is more readable than `1000000`, and `10_100_201_330` is easier to scan than `10100201330`. For more information, see [PEP-0515][underscores_notation]. + +2. Using exponential notation or scientific notation. The e (or E) character followed by an integer represents the power of 10 by which the number preceding the e should be multiplied (_**ie:** `1e6`, 1 is multiplied by 10 raised to the power of 6, which equals `1000000`_). For more details, check out this reference on [scientific notation][scientific_notation]. + + ## Dates and Times in Python This exercise explores objects from Python's `datetime` module: @@ -8,6 +18,8 @@ This exercise explores objects from Python's `datetime` module: - [datetime objects][datetime.datetime] - [timedelta objects][datetime.timedelta] -[datetime]: https://docs.python.org/3.9/library/datetime.html#module-datetime [datetime.datetime]: https://docs.python.org/3.9/library/datetime.html#datetime.datetime -[datetime.timedelta]: https://docs.python.org/3.9/library/datetime.html#timedelta-objects \ No newline at end of file +[datetime.timedelta]: https://docs.python.org/3.9/library/datetime.html#timedelta-objects +[datetime]: https://docs.python.org/3.9/library/datetime.html#module-datetime +[scientific_notation]: https://python-reference.readthedocs.io/en/latest/docs/float/scientific.html +[underscores_notation]: https://peps.python.org/pep-0515/#:~:text=The%20syntax%20would%20be%20the,width%20of%2010%20with%20*%20separator. diff --git a/exercises/practice/gigasecond/.meta/config.json b/exercises/practice/gigasecond/.meta/config.json index 59b2d30cc9..76af3dac04 100644 --- a/exercises/practice/gigasecond/.meta/config.json +++ b/exercises/practice/gigasecond/.meta/config.json @@ -15,7 +15,8 @@ "pheanex", "sjakobi", "tqa236", - "yawpitch" + "yawpitch", + "AndrewLawendy" ], "files": { "solution": [ From a5d6a9066e55ab188fe80b6e3248c10e95f94fe8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 29 Jul 2023 07:18:26 -0700 Subject: [PATCH 506/826] Update hints.md (#3479) Corrected broken/mis-spelled datetime.datetime reflink. --- exercises/practice/gigasecond/.docs/hints.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exercises/practice/gigasecond/.docs/hints.md b/exercises/practice/gigasecond/.docs/hints.md index d4891ef8eb..57f40ceee8 100644 --- a/exercises/practice/gigasecond/.docs/hints.md +++ b/exercises/practice/gigasecond/.docs/hints.md @@ -2,7 +2,7 @@ ## General -- Your function should parse the passed-in [datetime object][dtatetime], add a gigasecond's worth of time to it, and then return the result. +- Your function should parse the passed-in [datetime object][datetime.datetime], add a gigasecond's worth of time to it, and then return the result. - If you're having trouble, remember to take a look at the provided test cases under the Tests tab. These will help you figure out what the expected inputs and outputs of your function(s) should be. @@ -12,5 +12,7 @@ - Scientific notation can be more compact and easier to scan when there are very large numbers (_**ie:** `1e6`, 1 is multiplied by 10 raised to the power of 6, which equals `1000000`_). For more information, see this reference on [scientific notation][scientific_notation]. +[datetime.datetime]: https://docs.python.org/3.9/library/datetime.html#datetime.datetime [scientific_notation]: https://python-reference.readthedocs.io/en/latest/docs/float/scientific.html [underscores_notation]: https://peps.python.org/pep-0515/#:~:text=The%20syntax%20would%20be%20the,width%20of%2010%20with%20*%20separator. + From 6c9a7ea0f4f6d4d09aa5997bcfea374422aa0805 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 29 Jul 2023 07:29:34 -0700 Subject: [PATCH 507/826] Correct typo in reflink for hints file. (#3481) Seeing if a second attempt might help with #3480 --- exercises/practice/gigasecond/.docs/hints.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/exercises/practice/gigasecond/.docs/hints.md b/exercises/practice/gigasecond/.docs/hints.md index 57f40ceee8..30a36d1be9 100644 --- a/exercises/practice/gigasecond/.docs/hints.md +++ b/exercises/practice/gigasecond/.docs/hints.md @@ -2,7 +2,7 @@ ## General -- Your function should parse the passed-in [datetime object][datetime.datetime], add a gigasecond's worth of time to it, and then return the result. +- Your function should parse the passed-in [datetime object][datetime], add a gigasecond's worth of time to it, and then return the result. - If you're having trouble, remember to take a look at the provided test cases under the Tests tab. These will help you figure out what the expected inputs and outputs of your function(s) should be. @@ -12,7 +12,6 @@ - Scientific notation can be more compact and easier to scan when there are very large numbers (_**ie:** `1e6`, 1 is multiplied by 10 raised to the power of 6, which equals `1000000`_). For more information, see this reference on [scientific notation][scientific_notation]. -[datetime.datetime]: https://docs.python.org/3.9/library/datetime.html#datetime.datetime +[datetime]: https://docs.python.org/3.9/library/datetime.html#datetime.datetime [scientific_notation]: https://python-reference.readthedocs.io/en/latest/docs/float/scientific.html [underscores_notation]: https://peps.python.org/pep-0515/#:~:text=The%20syntax%20would%20be%20the,width%20of%2010%20with%20*%20separator. - From e3edaf8144161b7ce2b7337179edd903375070ec Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Mon, 31 Jul 2023 20:45:56 +0530 Subject: [PATCH 508/826] Approaches for Atbash Cipher (#3457) * first round * correct typo * correct yet another typo * Apply suggestions from code review Co-authored-by: BethanyG * replace most single variable names * replace i to index * Apply suggestions from code review Committing suggestions so the PR can be merged. --------- Co-authored-by: BethanyG --- .../atbash-cipher/.approaches/config.json | 21 ++++++++ .../atbash-cipher/.approaches/introduction.md | 46 +++++++++++++++++ .../.approaches/mono-function/content.md | 46 +++++++++++++++++ .../.approaches/mono-function/snippet.txt | 8 +++ .../.approaches/separate-functions/content.md | 51 +++++++++++++++++++ .../separate-functions/snippet.txt | 8 +++ 6 files changed, 180 insertions(+) create mode 100644 exercises/practice/atbash-cipher/.approaches/config.json create mode 100644 exercises/practice/atbash-cipher/.approaches/introduction.md create mode 100644 exercises/practice/atbash-cipher/.approaches/mono-function/content.md create mode 100644 exercises/practice/atbash-cipher/.approaches/mono-function/snippet.txt create mode 100644 exercises/practice/atbash-cipher/.approaches/separate-functions/content.md create mode 100644 exercises/practice/atbash-cipher/.approaches/separate-functions/snippet.txt diff --git a/exercises/practice/atbash-cipher/.approaches/config.json b/exercises/practice/atbash-cipher/.approaches/config.json new file mode 100644 index 0000000000..ed1edeb506 --- /dev/null +++ b/exercises/practice/atbash-cipher/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": ["safwansamsudeen"] + }, + "approaches": [ + { + "uuid": "920e6d08-e8fa-4bef-b2f4-837006c476ae", + "slug": "mono-function", + "title": "Mono-function", + "blurb": "Use one function for both tasks", + "authors": ["safwansamsudeen"] + }, + { + "uuid": "9a7a17e0-4ad6-4d97-a8b9-c74d47f3e000", + "slug": "separate-functions", + "title": "Separate Functions", + "blurb": "Use separate functions, and perhaps helper ones", + "authors": ["safwansamsudeen"] + } + ] +} diff --git a/exercises/practice/atbash-cipher/.approaches/introduction.md b/exercises/practice/atbash-cipher/.approaches/introduction.md new file mode 100644 index 0000000000..dff79f96c6 --- /dev/null +++ b/exercises/practice/atbash-cipher/.approaches/introduction.md @@ -0,0 +1,46 @@ +# Introduction +Atbash cipher in Python can be solved in many ways. + +## General guidance +The first thing is to have a "key" mapping - possibly in a `dict` or `str.maketrans`, otherwise the value would have to be calculated on the fly. +Then, you have to "clean" up the string to be encoded by removing numbers/whitespace. +Finally, you break it up into chunks of five before returning it. + +For decoding, it's similar - clean up (which automatically joins the chunks) and translate using the _same_ key - the realization that the same key can be used is crucial in solving this in an idiomatic manner. + +## Approach: separate functions +We use `str.maketrans` to create the encoding. +In `encode`, we use a [generator expression][generator expression] in `str.join`. +```python +from string import ascii_lowercase +ENCODING = str.maketrans(ascii_lowercase, ascii_lowercase[::-1]) + +def encode(text: str): + res = "".join(chr for chr in text.lower() if chr.isalnum()).translate(ENCODING) + return " ".join(res[index:index+5] for index in range(0, len(res), 5)) + +def decode(text: str): + return "".join(chr.lower() for chr in text if chr.isalnum()).translate(ENCODING) +``` +Read more on this [approach here][approach-seperate-functions]. + +## Approach: mono-function +Notice that there the majority of the code is repetitive? +A fun way to solve this would be to keep it all inside the `encode` function, and merely chunk it if `decode` is False: +For variation, this approach shows a different way to translate the text. +```python +from string import ascii_lowercase as asc_low +ENCODING = {chr: asc_low[id] for id, chr in enumerate(asc_low[::-1])} + +def encode(text: str, decode: bool = False): + res = "".join(ENCODING.get(chr, chr) for chr in text.lower() if chr.isalnum()) + return res if decode else " ".join(res[index:index+5] for index in range(0, len(res), 5)) + +def decode(text: str): + return encode(text, True) +``` +For more detail, [read here][approach-mono-function]. + +[approach-separate-functions]: https://exercism.org/tracks/python/exercises/atbash-cipher/approaches/separate-functions +[approach-mono-function]: https://exercism.org/tracks/python/exercises/atbash-cipher/approaches/mono-function +[generator expression]: https://www.programiz.com/python-programming/generator diff --git a/exercises/practice/atbash-cipher/.approaches/mono-function/content.md b/exercises/practice/atbash-cipher/.approaches/mono-function/content.md new file mode 100644 index 0000000000..879664ce20 --- /dev/null +++ b/exercises/practice/atbash-cipher/.approaches/mono-function/content.md @@ -0,0 +1,46 @@ +## Approach: Mono-function +Notice that there the majority of the code is repetitive? +A fun way to solve this would be to keep it all inside the `encode` function, and merely chunk it if `decode` is False: +For variation, this approach shows a different way to translate the text. +```python +from string import ascii_lowercase as asc_low +ENCODING = {chr: asc_low[id] for id, chr in enumerate(asc_low[::-1])} + +def encode(text: str, decode: bool = False): + res = "".join(ENCODING.get(chr, chr) for chr in text.lower() if chr.isalnum()) + return res if decode else " ".join(res[index:index+5] for index in range(0, len(res), 5)) + +def decode(text: str): + return encode(text, True) +``` +To explain the translation: we use a `dict` comprehension in which we reverse the ASCII lowercase digits, and enumerate through them - that is, `z` is 0, `y` is 1, and so on. +We access the character at that index and set it to the value of `c` - so `z` translates to `a`. + +In the calculation of the result, we try to obtain the value of the character using `dict.get`, which accepts a default parameter. +In this case, the character itself is the default - that is, numbers won't be found in the translation key, and thus should remain as numbers. + +We use a [ternary operator][ternary-operator] to check if we actually mean to decode the function, in which case we return the result as is. +If not, we chunk the result by joining every five characters with a space. + +Another possible way to solve this would be to use a function that returns a function that encodes or decodes based on the parameters: +```python +from string import ascii_lowercase as alc + +lowercase = {chr: alc[id] for id, chr in enumerate(alc[::-1])} + +def code(decode=False): + def func(text): + line = "".join(lowercase.get(chr, chr) for chr in text.lower() if chr.isalnum()) + return line if decode else " ".join(line[index:index+5] for index in range(0, len(line), 5)) + return func + + +encode = code() +decode = code(True) +``` +The logic is the same - we've instead used one function that generates two _other_ functions based on the boolean value of its parameter. +`encode` is set to the function that's returned, and performs encoding. +`decode` is set a function that _decodes_. + +[ternary-operator]: https://www.tutorialspoint.com/ternary-operator-in-python +[decorator]: https://realpython.com/primer-on-python-decorators/ \ No newline at end of file diff --git a/exercises/practice/atbash-cipher/.approaches/mono-function/snippet.txt b/exercises/practice/atbash-cipher/.approaches/mono-function/snippet.txt new file mode 100644 index 0000000000..84e8b79300 --- /dev/null +++ b/exercises/practice/atbash-cipher/.approaches/mono-function/snippet.txt @@ -0,0 +1,8 @@ +from string import ascii_lowercase as asc_low +ENCODING = {chr: asc_low[id] for id, chr in enumerate(asc_low[::-1])} + +def encode(text: str, decode: bool = False): + res = "".join(ENCODING.get(chr, chr) for chr in text.lower() if chr.isalnum()) + return res if decode else " ".join(res[index:index+5] for index in range(0, len(res), 5)) +def decode(text: str): + return encode(text, True) \ No newline at end of file diff --git a/exercises/practice/atbash-cipher/.approaches/separate-functions/content.md b/exercises/practice/atbash-cipher/.approaches/separate-functions/content.md new file mode 100644 index 0000000000..60e02a2205 --- /dev/null +++ b/exercises/practice/atbash-cipher/.approaches/separate-functions/content.md @@ -0,0 +1,51 @@ +## Approach: Separate Functions +We use `str.maketrans` to create the encoding. +`.maketrans`/`.translate` is extremely fast compared to other methods of translation. +If you're interested, [read more][str-maketrans] about it. + +In `encode`, we use a [generator expression][generator-expression] in `str.join`, which is more efficient - and neater - than a list comprehension. +```python +from string import ascii_lowercase +ENCODING = str.maketrans(ascii_lowercase, ascii_lowercase[::-1]) + +def encode(text: str): + res = "".join(chr for chr in text.lower() if chr.isalnum()).translate(ENCODING) + return " ".join(res[index:index+5] for index in range(0, len(res), 5)) + +def decode(text: str): + return "".join(chr.lower() for chr in text if chr.isalnum()).translate(ENCODING) +``` +In `encode`, we first join together every character if the character is alphanumeric - as we use `text.lower()`, the characters are all lowercase as needed. +Then, we translate it and return a version joining every five characters with a space in between. + +`decode` does the exact same thing, except it doesn't return a chunked output. +Instead of cleaning the input by checking that it's alphanumeric, we check that it's not a whitespace character. + +It might be cleaner to use helper functions: +```python +from string import ascii_lowercase +ENCODING = str.maketrans(ascii_lowercase, ascii_lowercase[::-1]) +def clean(text): + return "".join([chr.lower() for chr in text if chr.isalnum()]) +def chunk(text): + return " ".join(text[index:index+5] for index in range(0, len(text), 5)) + +def encode(text): + return chunk(clean(text).translate(ENCODING)) + +def decode(text): + return clean(text).translate(ENCODING) +``` +Note that checking that `chr` _is_ alphanumeric achieves the same result as checking that it's _not_ whitespace, although it's not as explicit. +As this is a helper function, this is acceptable enough. + +You can also make `chunk` recursive: +```python +def chunk(text): + if len(text) <= 5: + return text + return text[:5] + " " + chunk(text[5:]) +``` + +[generator-expression]: https://www.programiz.com/python-programming/generator +[str-maketrans]: https://www.programiz.com/python-programming/methods/string/maketrans \ No newline at end of file diff --git a/exercises/practice/atbash-cipher/.approaches/separate-functions/snippet.txt b/exercises/practice/atbash-cipher/.approaches/separate-functions/snippet.txt new file mode 100644 index 0000000000..fbfe0b75fa --- /dev/null +++ b/exercises/practice/atbash-cipher/.approaches/separate-functions/snippet.txt @@ -0,0 +1,8 @@ +from string import ascii_lowercase +ENCODING = str.maketrans(ascii_lowercase, ascii_lowercase[::-1]) + +def encode(text: str): + res = "".join(chr for chr in text.lower() if chr.isalnum()).translate(ENCODING) + return " ".join(res[index:index+5] for index in range(0, len(res), 5)) +def decode(text: str): + return "".join(chr.lower() for chr in text if not chr.isspace()).translate(ENCODING) \ No newline at end of file From b585577bcf0d0d4e033ce0ecc9dc95e064861d56 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:46:47 +0200 Subject: [PATCH 509/826] Fix recursion limit (#3485) --- .../.approaches/recursion/content.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md index cdfa25335c..173aef13c5 100644 --- a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md @@ -36,16 +36,20 @@ Then the `steps` function can execute the same code again with new values. Meaning we can get a long chain or stack of `1 + steps(number)` until the number reaches one and the code adds 0. That translates to something like this: `1 + 1 + 1 + 1 + 0`. +Python doesn't have [tail call optimization][tail-call]. +Which means that the stack of `1 + steps(number)` will grow until it reaches the recursion limit. + +~~~~exercism/caution In Python, we can't have a function call itself more than 1000 times by default. -Code that exceeds this recursion limit will throw a [RecursionError][recursion-error]. -While it is possible to adjust the [recursion limit][recursion-limit], doing so risks crashing Python and may also crash your system. +Code that exceeds this recursion limit will throw a [RecursionError](https://docs.python.org/3/library/exceptions.html#RecursionError). +While it is possible to adjust the [recursion limit][recursion-limit](https://docs.python.org/3/library/sys.html#sys.setrecursionlimit), doing so risks crashing Python and may also crash your system. Casually raising the recursion limit is not recommended. +~~~~ [clojure]: https://exercism.org/tracks/clojure [elixir]: https://exercism.org/tracks/elixir [haskell]: https://exercism.org/tracks/haskell [recursion]: https://realpython.com/python-thinking-recursively/ -[recursion-error]: https://docs.python.org/3/library/exceptions.html#RecursionError -[recursion-limit]: https://docs.python.org/3/library/sys.html#sys.setrecursionlimit +[tail-call]: https://en.wikipedia.org/wiki/Tail_call [ternary-operator]: https://exercism.org/tracks/python/exercises/collatz-conjecture/approaches/ternary-operator [value-error]: https://docs.python.org/3/library/exceptions.html#ValueError From 5da0cb4553e91ffc5c80145a0c2181943e9bc290 Mon Sep 17 00:00:00 2001 From: meatball <69751659+meatball133@users.noreply.github.com> Date: Sat, 5 Aug 2023 00:28:46 +0200 Subject: [PATCH 510/826] Fix link (#3486) --- .../collatz-conjecture/.approaches/recursion/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md index 173aef13c5..def361c895 100644 --- a/exercises/practice/collatz-conjecture/.approaches/recursion/content.md +++ b/exercises/practice/collatz-conjecture/.approaches/recursion/content.md @@ -42,7 +42,7 @@ Which means that the stack of `1 + steps(number)` will grow until it reaches the ~~~~exercism/caution In Python, we can't have a function call itself more than 1000 times by default. Code that exceeds this recursion limit will throw a [RecursionError](https://docs.python.org/3/library/exceptions.html#RecursionError). -While it is possible to adjust the [recursion limit][recursion-limit](https://docs.python.org/3/library/sys.html#sys.setrecursionlimit), doing so risks crashing Python and may also crash your system. +While it is possible to adjust the [recursion limit](https://docs.python.org/3/library/sys.html#sys.setrecursionlimit), doing so risks crashing Python and may also crash your system. Casually raising the recursion limit is not recommended. ~~~~ From f7fcf6fa0e01188a291818e30b9ce6aabad0bc80 Mon Sep 17 00:00:00 2001 From: Andrew Lawendy <22712035+AndrewLawendy@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:56:01 +0300 Subject: [PATCH 511/826] [Space Age]: Update introduction and instructions (#3483) * docs(space age): :memo: introduce specific instructions and move current instructions into introduction * docs(contributors): :memo: add my account * revert: :rewind: revert instructions * docs(append): :memo: append instructions * Added ref links and changed wording. Changed the wording a bit and added some links to the classes concept and the Python documentation. * Removed excess line --------- Co-authored-by: BethanyG --- .../space-age/.docs/instructions.append.md | 26 +++++++++++++++++++ .../practice/space-age/.meta/config.json | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/space-age/.docs/instructions.append.md diff --git a/exercises/practice/space-age/.docs/instructions.append.md b/exercises/practice/space-age/.docs/instructions.append.md new file mode 100644 index 0000000000..c72d2b3ca9 --- /dev/null +++ b/exercises/practice/space-age/.docs/instructions.append.md @@ -0,0 +1,26 @@ +# Instructions append + +For the Python track, this exercise asks you to create a `SpaceAge` _class_ (_[concept:python/classes]()_) that includes methods for all the planets of the solar system. +Methods should follow the naming convention `on_`. + +Each method should `return` the age (_"on" that planet_) in years, rounded to two decimal places: + +```python +#creating an instance with one billion seconds, and calling .on_earth(). +>>> SpaceAge(1000000000).on_earth() + +#This is one billion seconds on Earth in years +31.69 +``` + +For more information on constructing and using classes, see: + +- [**A First Look at Classes**][first look at classes] from the Python documentation. +- [**A Word About names and Objects**][names and objects] from the Python documentation. +- [**Objects, values, and types**][objects, values and types] in the Python data model documentation. +- [**What is a Class?**][what is a class] from Trey Hunners Python Morsels website. + +[first look at classes]: https://docs.python.org/3/tutorial/classes.html#a-first-look-at-classes +[names and objects]: https://docs.python.org/3/tutorial/classes.html#a-word-about-names-and-objects +[objects, values and types]: https://docs.python.org/3/reference/datamodel.html#objects-values-and-types +[what is a class]: https://www.pythonmorsels.com/what-is-a-class/ diff --git a/exercises/practice/space-age/.meta/config.json b/exercises/practice/space-age/.meta/config.json index 174af6bfc3..2c6189d870 100644 --- a/exercises/practice/space-age/.meta/config.json +++ b/exercises/practice/space-age/.meta/config.json @@ -14,7 +14,8 @@ "N-Parsons", "pheanex", "sjakobi", - "tqa236" + "tqa236", + "AndrewLawendy" ], "files": { "solution": [ From 765236b29ace33504475dd515e04a7d785d89472 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 13 Aug 2023 22:48:07 -0700 Subject: [PATCH 512/826] [Track Config]: Another pass at re-ordering practice exercises. (#3487) * Another pass at re-ordering practice exercises. * Cleaned up errors with array commas and formatted with Prettier. * Cleaned up errors with duplicate loops key in prereqs. --- config.json | 1441 +++++++++++++++++++++++++-------------------------- 1 file changed, 718 insertions(+), 723 deletions(-) diff --git a/config.json b/config.json index 36f517f949..871f4c8429 100644 --- a/config.json +++ b/config.json @@ -237,38 +237,6 @@ "prerequisites": [], "difficulty": 1 }, - { - "slug": "reverse-string", - "name": "Reverse String", - "uuid": "d39f86fe-db56-461c-8a93-d87058af8366", - "practices": [], - "prerequisites": [ - "basics", - "bools", - "conditionals", - "lists", - "list-methods", - "loops", - "strings" - ], - "difficulty": 1 - }, - { - "slug": "resistor-color", - "name": "Resistor Color", - "uuid": "d17bee9c-e803-4745-85ea-864f255fb04e", - "practices": ["lists"], - "prerequisites": ["strings", "lists"], - "difficulty": 1 - }, - { - "slug": "two-fer", - "name": "Two Fer", - "uuid": "4177de10-f767-4306-b45d-5e9c08ef4753", - "practices": ["string-formatting", "function-arguments"], - "prerequisites": ["basics"], - "difficulty": 1 - }, { "slug": "leap", "name": "Leap", @@ -278,33 +246,11 @@ "difficulty": 1 }, { - "slug": "resistor-color-duo", - "name": "Resistor Color Duo", - "uuid": "089f06a6-0759-479c-8c00-d699525a1e22", - "practices": ["list-methods"], - "prerequisites": [ - "basics", - "bools", - "lists", - "list-methods", - "numbers" - ], - "difficulty": 1 - }, - { - "slug": "pangram", - "name": "Pangram", - "uuid": "bebf7ae6-1c35-48bc-926b-e053a975eb10", - "practices": ["strings"], - "prerequisites": ["basics", "bools", "conditionals", "strings"], - "difficulty": 1 - }, - { - "slug": "isogram", - "name": "Isogram", - "uuid": "d1a98c79-d3cc-4035-baab-0e334d2b6a57", - "practices": ["strings"], - "prerequisites": ["strings"], + "slug": "triangle", + "name": "Triangle", + "uuid": "f0bc144f-3226-4e53-93ee-e60316b29e31", + "practices": ["bools"], + "prerequisites": ["basics", "bools", "numbers"], "difficulty": 1 }, { @@ -316,21 +262,19 @@ "difficulty": 1 }, { - "slug": "hamming", - "name": "Hamming", - "uuid": "8648fa0c-d85f-471b-a3ae-0f8c05222c89", - "practices": [ - "generator-expressions", - "raising-and-handling-errors", - "sequences" - ], - "prerequisites": [ - "basics", - "loops", - "lists", - "conditionals", - "numbers" - ], + "slug": "armstrong-numbers", + "name": "Armstrong Numbers", + "uuid": "d9ceb246-b518-42b9-9fa3-112e25c7ecd8", + "practices": ["numbers"], + "prerequisites": ["basics", "numbers"], + "difficulty": 1 + }, + { + "slug": "collatz-conjecture", + "name": "Collatz Conjecture", + "uuid": "33f689ee-1d9c-4908-a71c-f84bff3510df", + "practices": ["numbers"], + "prerequisites": ["basics", "numbers"], "difficulty": 1 }, { @@ -342,78 +286,78 @@ "difficulty": 1 }, { - "slug": "rna-transcription", - "name": "RNA Transcription", - "uuid": "ca7c5ef1-5135-4fb4-8e68-669ef0f2a51a", - "practices": ["string-methods"], - "prerequisites": [ - "basics", - "bools", - "conditionals", - "loops", - "strings", - "string-methods" - ], + "slug": "raindrops", + "name": "Raindrops", + "uuid": "82d82e32-cb30-4119-8862-d019563dd1e3", + "practices": ["conditionals"], + "prerequisites": ["basics", "numbers", "conditionals", "bools"], "difficulty": 1 }, { - "slug": "armstrong-numbers", - "name": "Armstrong Numbers", - "uuid": "d9ceb246-b518-42b9-9fa3-112e25c7ecd8", - "practices": ["numbers"], - "prerequisites": ["basics", "numbers"], + "slug": "darts", + "name": "Darts", + "uuid": "cb581e2c-66ab-4221-9884-44bacb7c4ebe", + "practices": ["comparisons"], + "prerequisites": ["basics", "numbers", "comparisons", "conditionals"], "difficulty": 1 }, { - "slug": "etl", - "name": "ETL", - "uuid": "a3b24ef2-303a-494e-8804-e52a67ef406b", - "practices": ["dicts"], - "prerequisites": ["dicts"], + "slug": "perfect-numbers", + "name": "Perfect Numbers", + "uuid": "c23ae7a3-3095-4608-8720-ee9ce8938f26", + "practices": ["comparisons"], + "prerequisites": ["basics", "bools", "conditionals", "numbers"], "difficulty": 1 }, { - "slug": "darts", - "name": "Darts", - "uuid": "cb581e2c-66ab-4221-9884-44bacb7c4ebe", - "practices": ["comparisons"], - "prerequisites": ["basics", "numbers", "comparisons", "conditionals"], + "slug": "reverse-string", + "name": "Reverse String", + "uuid": "d39f86fe-db56-461c-8a93-d87058af8366", + "practices": ["sequences"], + "prerequisites": ["basics", "bools", "conditionals", "strings"], "difficulty": 1 }, { - "slug": "raindrops", - "name": "Raindrops", - "uuid": "82d82e32-cb30-4119-8862-d019563dd1e3", - "practices": ["conditionals"], - "prerequisites": ["basics", "numbers", "conditionals", "bools"], + "slug": "pangram", + "name": "Pangram", + "uuid": "bebf7ae6-1c35-48bc-926b-e053a975eb10", + "practices": ["strings"], + "prerequisites": ["basics", "bools", "conditionals", "strings"], "difficulty": 1 }, { - "slug": "sum-of-multiples", - "name": "Sum of Multiples", - "uuid": "6e0caa0a-6a1a-4f03-bf0f-e07711f4b069", - "practices": ["sets"], - "prerequisites": [ - "basics", - "conditionals", - "lists", - "loops", - "numbers", - "sets" - ], + "slug": "isogram", + "name": "Isogram", + "uuid": "d1a98c79-d3cc-4035-baab-0e334d2b6a57", + "practices": ["strings"], + "prerequisites": ["strings"], "difficulty": 1 }, { - "slug": "anagram", - "name": "Anagram", - "uuid": "43eaf8bd-0b4d-4ea9-850a-773f013325ef", - "practices": ["list-comprehensions"], + "slug": "isbn-verifier", + "name": "ISBN Verifier", + "uuid": "7961c852-c87a-44b0-b152-efea3ac8555c", + "practices": ["strings"], + "prerequisites": ["basics", "bools", "conditionals", "strings"], + "difficulty": 1 + }, + { + "slug": "rotational-cipher", + "name": "Rotational Cipher", + "uuid": "4c408aab-80b9-475d-9c06-b01cd0fcd08f", + "practices": ["strings"], + "prerequisites": ["basics", "conditionals", "numbers", "strings"], + "difficulty": 1 + }, + { + "slug": "rna-transcription", + "name": "RNA Transcription", + "uuid": "ca7c5ef1-5135-4fb4-8e68-669ef0f2a51a", + "practices": ["string-methods"], "prerequisites": [ "basics", "bools", "conditionals", - "lists", - "list-methods", "loops", "strings", "string-methods" @@ -421,73 +365,66 @@ "difficulty": 1 }, { - "slug": "difference-of-squares", - "name": "Difference of Squares", - "uuid": "913b6099-d75a-4c27-8243-476081752c31", - "practices": ["numbers"], - "prerequisites": ["basics", "numbers"], - "difficulty": 1 - }, - { - "slug": "flatten-array", - "name": "Flatten Array", - "uuid": "07481204-fe88-4aa2-995e-d40d1ae15070", - "practices": ["lists"], + "slug": "wordy", + "name": "Wordy", + "uuid": "af50bb9a-e400-49ce-966f-016c31720be1", + "practices": ["string-methods"], "prerequisites": [ "basics", - "conditionals", - "strings", "lists", - "loops" + "loops", + "strings", + "string-methods", + "numbers" ], "difficulty": 1 }, { - "slug": "perfect-numbers", - "name": "Perfect Numbers", - "uuid": "c23ae7a3-3095-4608-8720-ee9ce8938f26", - "practices": ["comparisons"], - "prerequisites": ["basics", "bools", "conditionals", "numbers"], - "difficulty": 1 - }, - { - "slug": "gigasecond", - "name": "Gigasecond", - "uuid": "22606e91-57f3-44cf-ab2d-94f6ee6402e8", - "practices": [], - "prerequisites": ["classes"], + "slug": "resistor-color", + "name": "Resistor Color", + "uuid": "d17bee9c-e803-4745-85ea-864f255fb04e", + "practices": ["lists"], + "prerequisites": ["strings", "lists"], "difficulty": 1 }, { - "slug": "isbn-verifier", - "name": "ISBN Verifier", - "uuid": "7961c852-c87a-44b0-b152-efea3ac8555c", - "practices": ["strings"], - "prerequisites": ["basics", "bools", "conditionals", "strings"], + "slug": "resistor-color-duo", + "name": "Resistor Color Duo", + "uuid": "089f06a6-0759-479c-8c00-d699525a1e22", + "practices": ["lists"], + "prerequisites": ["basics", "bools", "lists", "numbers", "strings"], "difficulty": 1 }, { - "slug": "space-age", - "name": "Space Age", - "uuid": "f8303c4d-bbbb-495b-b61b-0f617f7c9a13", - "practices": ["dicts"], + "slug": "resistor-color-trio", + "name": "Resistor Color Trio", + "uuid": "f987a5b7-96f2-49c2-99e8-aef30d23dd58", + "practices": ["list-methods"], "prerequisites": [ "basics", "bools", - "dicts", "lists", "list-methods", - "loops", - "numbers" + "numbers", + "strings", + "comparisons" ], "difficulty": 1 }, { - "slug": "collatz-conjecture", - "name": "Collatz Conjecture", - "uuid": "33f689ee-1d9c-4908-a71c-f84bff3510df", - "practices": ["numbers"], - "prerequisites": ["basics", "numbers"], + "slug": "resistor-color-expert", + "name": "Resistor Color Expert", + "uuid": "8a738365-0efa-444f-9466-a757ddaddcdb", + "practices": ["list-methods"], + "prerequisites": [ + "basics", + "bools", + "lists", + "list-methods", + "numbers", + "strings", + "comparisons" + ], "difficulty": 1 }, { @@ -499,37 +436,32 @@ "basics", "bools", "conditionals", - "lists", "list-methods", + "lists", + "loops", "numbers", - "strings", - "string-methods" + "string-methods", + "strings" ], "difficulty": 1 }, { - "slug": "wordy", - "name": "Wordy", - "uuid": "af50bb9a-e400-49ce-966f-016c31720be1", - "practices": ["string-methods"], + "slug": "anagram", + "name": "Anagram", + "uuid": "43eaf8bd-0b4d-4ea9-850a-773f013325ef", + "practices": ["list-methods"], "prerequisites": [ "basics", + "bools", + "conditionals", + "list-methods", "lists", "loops", - "strings", "string-methods", - "numbers" + "strings" ], "difficulty": 1 }, - { - "slug": "triangle", - "name": "Triangle", - "uuid": "f0bc144f-3226-4e53-93ee-e60316b29e31", - "practices": ["bools"], - "prerequisites": ["basics", "bools", "numbers"], - "difficulty": 1 - }, { "slug": "house", "name": "House", @@ -545,14 +477,6 @@ ], "difficulty": 1 }, - { - "slug": "rotational-cipher", - "name": "Rotational Cipher", - "uuid": "4c408aab-80b9-475d-9c06-b01cd0fcd08f", - "practices": ["strings"], - "prerequisites": ["basics", "conditionals", "numbers", "strings"], - "difficulty": 1 - }, { "slug": "binary-search", "name": "Binary Search", @@ -570,20 +494,116 @@ ], "difficulty": 1 }, + { + "slug": "hamming", + "name": "Hamming", + "uuid": "8648fa0c-d85f-471b-a3ae-0f8c05222c89", + "practices": [ + "generator-expressions", + "raising-and-handling-errors", + "sequences" + ], + "prerequisites": [ + "basics", + "lists", + "loops", + "conditionals", + "numbers" + ], + "difficulty": 1 + }, + { + "slug": "flatten-array", + "name": "Flatten Array", + "uuid": "07481204-fe88-4aa2-995e-d40d1ae15070", + "practices": [], + "prerequisites": [ + "basics", + "conditionals", + "strings", + "lists", + "list-methods", + "loops" + ], + "difficulty": 1 + }, + { + "slug": "difference-of-squares", + "name": "Difference of Squares", + "uuid": "913b6099-d75a-4c27-8243-476081752c31", + "practices": [], + "prerequisites": ["basics", "numbers", "loops"], + "difficulty": 1 + }, { "slug": "list-ops", "name": "List Ops", "uuid": "818c6472-b734-4ff4-8016-ce540141faec", - "practices": ["list-methods"], + "practices": [], "prerequisites": ["conditionals", "lists", "list-methods", "loops"], "difficulty": 1 }, { - "slug": "acronym", - "name": "Acronym", - "uuid": "038c7f7f-02f6-496f-9e16-9372621cc4cd", - "practices": ["regular-expressions"], - "prerequisites": ["basics", "loops", "strings", "string-methods"], + "slug": "etl", + "name": "ETL", + "uuid": "a3b24ef2-303a-494e-8804-e52a67ef406b", + "practices": ["dicts"], + "prerequisites": ["dicts"], + "difficulty": 1 + }, + { + "slug": "space-age", + "name": "Space Age", + "uuid": "f8303c4d-bbbb-495b-b61b-0f617f7c9a13", + "practices": ["dicts"], + "prerequisites": [ + "basics", + "bools", + "dicts", + "lists", + "list-methods", + "loops", + "numbers" + ], + "difficulty": 1 + }, + { + "slug": "sum-of-multiples", + "name": "Sum of Multiples", + "uuid": "6e0caa0a-6a1a-4f03-bf0f-e07711f4b069", + "practices": ["sets"], + "prerequisites": [ + "basics", + "conditionals", + "lists", + "loops", + "numbers", + "sets" + ], + "difficulty": 1 + }, + { + "slug": "gigasecond", + "name": "Gigasecond", + "uuid": "22606e91-57f3-44cf-ab2d-94f6ee6402e8", + "practices": [], + "prerequisites": ["classes"], + "difficulty": 1 + }, + { + "slug": "two-fer", + "name": "Two Fer", + "uuid": "4177de10-f767-4306-b45d-5e9c08ef4753", + "practices": ["function-arguments"], + "prerequisites": ["basics", "sets"], + "difficulty": 1 + }, + { + "slug": "square-root", + "name": "Square Root", + "uuid": "c32f994a-1080-4f05-bf88-051975a75d64", + "practices": ["numbers"], + "prerequisites": ["basics", "numbers", "conditionals", "loops"], "difficulty": 2 }, { @@ -595,45 +615,32 @@ "difficulty": 2 }, { - "slug": "protein-translation", - "name": "Protein Translation", - "uuid": "c89243f3-703e-4fe0-8e43-f200eedf2825", - "practices": ["loops"], + "slug": "matching-brackets", + "name": "Matching Brackets", + "uuid": "45229a7c-6703-4240-8287-16645881a043", + "practices": ["conditionals"], "prerequisites": [ "basics", + "bools", "conditionals", "lists", + "list-methods", "loops", - "strings", - "string-methods" + "strings" ], "difficulty": 2 }, { - "slug": "square-root", - "name": "Square Root", - "uuid": "c32f994a-1080-4f05-bf88-051975a75d64", - "practices": ["numbers"], + "slug": "sublist", + "name": "Sublist", + "uuid": "cc5eb848-09bc-458c-8fb6-3a17687cb4eb", + "practices": ["comparisons"], "prerequisites": [ "basics", - "numbers", + "bools", "conditionals", - "loops" - ], - "difficulty": 2 - }, - { - "slug": "scrabble-score", - "name": "Scrabble Score", - "uuid": "d081446b-f26b-41a2-ab7f-dd7f6736ecfe", - "practices": ["regular-expressions"], - "prerequisites": [ - "basics", - "lists", - "loops", - "dicts", - "strings", - "string-methods" + "comparisons", + "lists" ], "difficulty": 2 }, @@ -654,96 +661,76 @@ "difficulty": 2 }, { - "slug": "resistor-color-trio", - "name": "Resistor Color Trio", - "uuid": "f987a5b7-96f2-49c2-99e8-aef30d23dd58", - "practices": ["list-methods"], + "slug": "diamond", + "name": "Diamond", + "uuid": "a7bc6837-59e4-46a1-89a2-a5aa44f5e66e", + "practices": ["lists"], "prerequisites": [ "basics", "bools", + "conditionals", "lists", - "numbers", - "strings", - "comparisons" - ], - "difficulty": 2 - }, - { - "slug": "word-count", - "name": "Word Count", - "uuid": "04316811-0bc3-4377-8ff5-5a300ba41d61", - "practices": ["dicts"], - "prerequisites": [ - "basics", "loops", "strings", - "string-methods", - "dicts" + "string-methods" ], "difficulty": 2 }, { - "slug": "proverb", - "name": "Proverb", - "uuid": "9fd94229-f974-45bb-97ea-8bfe484f6eb3", - "practices": ["unpacking-and-multiple-assignment"], - "prerequisites": ["dicts", - "unpacking-and-multiple-assignment"], - "difficulty": 2 - }, - { - "slug": "robot-name", - "name": "Robot Name", - "uuid": "bf30b17f-6b71-4bb5-815a-88f8181b89ae", - "practices": [], + "slug": "protein-translation", + "name": "Protein Translation", + "uuid": "c89243f3-703e-4fe0-8e43-f200eedf2825", + "practices": ["loops"], "prerequisites": [ "basics", - "bools", "conditionals", - "classes", "lists", "loops", - "sets", "strings", "string-methods" ], "difficulty": 2 }, { - "slug": "nth-prime", - "name": "Nth Prime", - "uuid": "a20924d2-fe6d-4714-879f-3239feb9d2f2", + "slug": "prime-factors", + "name": "Prime Factors", + "uuid": "41dd9178-76b4-4f78-b71a-b5ff8d12645b", "practices": [], "prerequisites": [ "basics", - "bools", "conditionals", - "comparisons", "lists", "list-methods", "loops", - "numbers", - "strings" + "numbers" ], "difficulty": 2 }, { - "slug": "twelve-days", - "name": "Twelve Days", - "uuid": "d41238ce-359c-4a9a-81ea-ca5d2c4bb50d", - "practices": ["tuples"], + "slug": "say", + "name": "Say", + "uuid": "2f86ce8e-47c7-4858-89fc-e7729feb0f2f", + "practices": [], "prerequisites": [ "basics", "conditionals", + "dicts", "lists", - "list-methods", "loops", + "numbers", "strings", - "string-methods", - "tuples" + "string-methods" ], "difficulty": 2 }, + { + "slug": "acronym", + "name": "Acronym", + "uuid": "038c7f7f-02f6-496f-9e16-9372621cc4cd", + "practices": ["regular-expressions"], + "prerequisites": ["basics", "loops", "strings", "string-methods"], + "difficulty": 2 + }, { "slug": "series", "name": "Series", @@ -760,46 +747,17 @@ "difficulty": 2 }, { - "slug": "phone-number", - "name": "Phone Number", - "uuid": "f384c6f8-987d-41a2-b504-e50506585526", - "practices": ["raising-and-handling-errors", "string-formatting"], - "prerequisites": [ - "basics", - "classes", - "lists", - "loops", - "numbers", - "strings", - "string-methods" - ], - "difficulty": 2 - }, - { - "slug": "matching-brackets", - "name": "Matching Brackets", - "uuid": "45229a7c-6703-4240-8287-16645881a043", - "practices": ["conditionals"], + "slug": "run-length-encoding", + "name": "Run-Length Encoding", + "uuid": "505e7bdb-e18d-45fd-9849-0bf33492efd9", + "practices": ["iteration", "regular-expressions"], "prerequisites": [ "basics", "bools", "conditionals", "lists", + "list-methods", "loops", - "strings" - ], - "difficulty": 2 - }, - { - "slug": "say", - "name": "Say", - "uuid": "2f86ce8e-47c7-4858-89fc-e7729feb0f2f", - "practices": ["lists"], - "prerequisites": [ - "basics", - "conditionals", - "dicts", - "lists", "numbers", "strings", "string-methods" @@ -807,84 +765,66 @@ "difficulty": 2 }, { - "slug": "queen-attack", - "name": "Queen Attack", - "uuid": "b280c252-5320-4e53-8294-1385d564eb02", + "slug": "nth-prime", + "name": "Nth Prime", + "uuid": "a20924d2-fe6d-4714-879f-3239feb9d2f2", "practices": [], "prerequisites": [ + "basics", "bools", "conditionals", - "classes", + "comparisons", + "lists", + "list-methods", + "loops", "numbers", - "strings", - "string-methods" + "strings" ], "difficulty": 2 }, { - "slug": "run-length-encoding", - "name": "Run-Length Encoding", - "uuid": "505e7bdb-e18d-45fd-9849-0bf33492efd9", - "practices": ["iteration", "regular-expressions"], + "slug": "twelve-days", + "name": "Twelve Days", + "uuid": "d41238ce-359c-4a9a-81ea-ca5d2c4bb50d", + "practices": ["tuples"], "prerequisites": [ "basics", - "bools", "conditionals", "lists", "list-methods", "loops", - "numbers", "strings", - "string-methods" + "string-methods", + "tuples" ], "difficulty": 2 }, { - "slug": "luhn", - "name": "Luhn", - "uuid": "34dde040-672e-472f-bf2e-b87b6f9933c0", - "practices": ["classes"], + "slug": "roman-numerals", + "name": "Roman Numerals", + "uuid": "bffe2007-717a-44ee-b628-b9c86a5001e8", + "practices": ["tuples"], "prerequisites": [ "basics", - "bools", "conditionals", - "classes", "lists", "list-methods", "loops", + "numbers", "strings", "string-methods", - "numbers" + "tuples" ], "difficulty": 2 }, { - "slug": "yacht", - "name": "Yacht", - "uuid": "22f937e5-52a7-4956-9dde-61c985251a6b", - "practices": ["enums"], - "prerequisites": ["basics", "bools", "numbers", "classes"], - "difficulty": 2 - }, - { - "slug": "sublist", - "name": "Sublist", - "uuid": "cc5eb848-09bc-458c-8fb6-3a17687cb4eb", - "practices": ["comparisons"], - "prerequisites": ["basics", "bools", "conditionals", "comparisons"], - "difficulty": 2 - }, - { - "slug": "diamond", - "name": "Diamond", - "uuid": "a7bc6837-59e4-46a1-89a2-a5aa44f5e66e", - "practices": ["lists"], + "slug": "word-count", + "name": "Word Count", + "uuid": "04316811-0bc3-4377-8ff5-5a300ba41d61", + "practices": ["dicts"], "prerequisites": [ "basics", - "bools", - "conditionals", "dicts", - "lists", "loops", "strings", "string-methods" @@ -892,34 +832,43 @@ "difficulty": 2 }, { - "slug": "transpose", - "name": "Transpose", - "uuid": "dc6e61a2-e9b9-4406-ba5c-188252afbba1", - "practices": ["list-methods"], + "slug": "scrabble-score", + "name": "Scrabble Score", + "uuid": "d081446b-f26b-41a2-ab7f-dd7f6736ecfe", + "practices": ["regular-expressions"], "prerequisites": [ "basics", - "bools", - "conditionals", + "dicts", "lists", - "list-methods", "loops", - "numbers", - "strings", - "string-methods" + "string-methods", + "strings" ], "difficulty": 2 }, { - "slug": "prime-factors", - "name": "Prime Factors", - "uuid": "41dd9178-76b4-4f78-b71a-b5ff8d12645b", - "practices": [], + "slug": "proverb", + "name": "Proverb", + "uuid": "9fd94229-f974-45bb-97ea-8bfe484f6eb3", + "practices": ["unpacking-and-multiple-assignment"], + "prerequisites": ["dicts", "unpacking-and-multiple-assignment"], + "difficulty": 2 + }, + { + "slug": "luhn", + "name": "Luhn", + "uuid": "34dde040-672e-472f-bf2e-b87b6f9933c0", + "practices": ["classes"], "prerequisites": [ "basics", + "bools", "conditionals", + "classes", "lists", "list-methods", "loops", + "strings", + "string-methods", "numbers" ], "difficulty": 2 @@ -943,64 +892,106 @@ "difficulty": 2 }, { - "slug": "roman-numerals", - "name": "Roman Numerals", - "uuid": "bffe2007-717a-44ee-b628-b9c86a5001e8", - "practices": ["tuples"], + "slug": "robot-name", + "name": "Robot Name", + "uuid": "bf30b17f-6b71-4bb5-815a-88f8181b89ae", + "practices": [], "prerequisites": [ "basics", + "bools", + "classes", "conditionals", - "tuples", "lists", - "list-methods", + "loops", + "sets", + "string-methods", + "strings" + ], + "difficulty": 2 + }, + { + "slug": "phone-number", + "name": "Phone Number", + "uuid": "f384c6f8-987d-41a2-b504-e50506585526", + "practices": ["raising-and-handling-errors", "string-formatting"], + "prerequisites": [ + "basics", + "classes", + "lists", "loops", "numbers", "strings", + "string-formatting", "string-methods" ], "difficulty": 2 }, { - "slug": "simple-cipher", - "name": "Simple Cipher", - "uuid": "09b2f396-00d7-4d89-ac47-5c444e00dd99", + "slug": "queen-attack", + "name": "Queen Attack", + "uuid": "b280c252-5320-4e53-8294-1385d564eb02", "practices": [], "prerequisites": [ - "basics", + "bools", "conditionals", "classes", - "lists", - "list-methods", - "loops", "numbers", "strings", "string-methods" ], - "difficulty": 3 + "difficulty": 2 }, { - "slug": "resistor-color-expert", - "name": "Resistor Color Expert", - "uuid": "8a738365-0efa-444f-9466-a757ddaddcdb", - "practices": ["list-methods"], + "slug": "transpose", + "name": "Transpose", + "uuid": "dc6e61a2-e9b9-4406-ba5c-188252afbba1", + "practices": ["unpacking-and-multiple-assignment"], "prerequisites": [ "basics", "bools", + "conditionals", "lists", + "list-methods", + "loops", "numbers", "strings", - "comparisons" + "string-methods", + "unpacking-and-multiple-assignment" + ], + "difficulty": 2 + }, + { + "slug": "yacht", + "name": "Yacht", + "uuid": "22f937e5-52a7-4956-9dde-61c985251a6b", + "practices": ["enums"], + "prerequisites": ["basics", "bools", "numbers", "classes"], + "difficulty": 2 + }, + { + "slug": "saddle-points", + "name": "Saddle Points", + "uuid": "71c96c5f-f3b6-4358-a9c6-fc625e2edda2", + "practices": ["loops"], + "prerequisites": [ + "basics", + "conditionals", + "lists", + "list-methods", + "loops", + "sets" ], "difficulty": 3 }, { - "slug": "matrix", - "name": "Matrix", - "uuid": "b564927a-f08f-4287-9e8d-9bd5daa7081f", - "practices": ["classes"], + "slug": "ocr-numbers", + "name": "OCR Numbers", + "uuid": "98ca48ed-5818-442c-bce1-308c8b3b3b77", + "practices": ["loops"], "prerequisites": [ "basics", - "classes", + "bools", + "conditionals", "lists", "list-methods", "loops", @@ -1011,199 +1002,214 @@ "difficulty": 3 }, { - "slug": "allergies", - "name": "Allergies", - "uuid": "83627e35-4689-4d9b-a81b-284c2c084466", - "practices": [], + "slug": "robot-simulator", + "name": "Robot Simulator", + "uuid": "ca474c47-57bb-4995-bf9a-b6937479de29", + "practices": ["class-customization", "decorators", "dict-methods"], "prerequisites": [ "basics", "conditionals", "classes", "dicts", + "lists", "loops", - "numbers" + "numbers", + "tuples" ], "difficulty": 3 }, { - "slug": "high-scores", - "name": "High Scores", - "uuid": "574d6323-5ff5-4019-9ebe-0067daafba13", - "practices": ["classes"], - "prerequisites": ["basics", "lists", "list-methods", "classes"], + "slug": "grade-school", + "name": "Grade School", + "uuid": "aadde1a8-ed7a-4242-bfc0-6dddfd382cf3", + "practices": ["collections", "dict-methods"], + "prerequisites": [ + "basics", + "dicts", + "lists", + "list-methods", + "classes" + ], "difficulty": 3 }, { - "slug": "crypto-square", - "name": "Crypto Square", - "uuid": "e8685468-8006-480f-87c6-6295700def38", - "practices": ["list-comprehensions"], + "slug": "sieve", + "name": "Sieve", + "uuid": "ad0192e6-7742-4922-a53e-791e25eb9ba3", + "practices": ["sets"], "prerequisites": [ + "basics", "conditionals", "lists", "list-methods", "loops", "numbers", - "strings", - "string-methods" + "sets" ], "difficulty": 3 }, { - "slug": "bottle-song", - "name": "Bottle Song", - "uuid": "70bec74a-0677-40c9-b9c9-cbcc49a2eae4", - "practices": ["generators"], + "slug": "pythagorean-triplet", + "name": "Pythagorean Triplet", + "uuid": "7b53865e-a981-46e0-8e47-6f8e1f3854b3", + "practices": ["sets"], "prerequisites": [ "basics", + "bools", "conditionals", - "dicts", "lists", "list-methods", "loops", "numbers", - "strings", - "string-methods", - "tuples" + "sets" ], "difficulty": 3 }, { - "slug": "poker", - "name": "Poker", - "uuid": "dcc0ee26-e384-4bd4-8c4b-613fa0bb8188", - "practices": ["functions", "higher-order-functions"], + "slug": "circular-buffer", + "name": "Circular Buffer", + "uuid": "77ee3b0e-a4e9-4257-bcfc-ff2c8f1477ab", + "practices": [ + "class-inheritance", + "function-arguments", + "user-defined-errors" + ], "prerequisites": [ "basics", "bools", "conditionals", "classes", + "dicts", "lists", "list-methods", "loops", - "numbers" + "numbers", + "strings" ], "difficulty": 3 }, { - "slug": "kindergarten-garden", - "name": "Kindergarten Garden", - "uuid": "42a2916c-ef03-44ac-b6d8-7eda375352c2", + "slug": "matrix", + "name": "Matrix", + "uuid": "b564927a-f08f-4287-9e8d-9bd5daa7081f", "practices": ["classes"], "prerequisites": [ "basics", "classes", - "dicts", + "lists", + "list-methods", "loops", + "numbers", "strings", "string-methods" ], "difficulty": 3 }, { - "slug": "saddle-points", - "name": "Saddle Points", - "uuid": "71c96c5f-f3b6-4358-a9c6-fc625e2edda2", - "practices": ["loops"], + "slug": "high-scores", + "name": "High Scores", + "uuid": "574d6323-5ff5-4019-9ebe-0067daafba13", + "practices": ["classes"], + "prerequisites": ["basics", "lists", "list-methods", "classes"], + "difficulty": 3 + }, + { + "slug": "kindergarten-garden", + "name": "Kindergarten Garden", + "uuid": "42a2916c-ef03-44ac-b6d8-7eda375352c2", + "practices": ["classes"], "prerequisites": [ "basics", - "conditionals", - "lists", - "list-methods", + "classes", + "dicts", "loops", - "sets" + "strings", + "string-methods" ], "difficulty": 3 }, { - "slug": "robot-simulator", - "name": "Robot Simulator", - "uuid": "ca474c47-57bb-4995-bf9a-b6937479de29", - "practices": ["class-customization", "decorators", "dict-methods"], + "slug": "bottle-song", + "name": "Bottle Song", + "uuid": "70bec74a-0677-40c9-b9c9-cbcc49a2eae4", + "practices": ["list-methods"], "prerequisites": [ "basics", "conditionals", - "classes", "dicts", "lists", + "list-methods", "loops", "numbers", + "strings", + "string-methods", "tuples" ], "difficulty": 3 }, { - "slug": "rectangles", - "name": "Rectangles", - "uuid": "4bebdd8d-a032-4993-85c5-7cc74fc89312", - "practices": ["iteration", "itertools", "sequences"], + "slug": "allergies", + "name": "Allergies", + "uuid": "83627e35-4689-4d9b-a81b-284c2c084466", + "practices": [], "prerequisites": [ "basics", - "bools", "conditionals", "classes", - "lists", - "list-methods", + "dicts", "loops", - "numbers", - "strings", - "string-methods", - "sets", - "tuples" + "numbers" ], "difficulty": 3 }, { - "slug": "sieve", - "name": "Sieve", - "uuid": "ad0192e6-7742-4922-a53e-791e25eb9ba3", - "practices": ["sets"], + "slug": "simple-cipher", + "name": "Simple Cipher", + "uuid": "09b2f396-00d7-4d89-ac47-5c444e00dd99", + "practices": [], "prerequisites": [ "basics", "conditionals", + "classes", "lists", "list-methods", "loops", "numbers", - "sets" + "strings", + "string-methods" ], "difficulty": 3 }, - { - "slug": "grade-school", - "name": "Grade School", - "uuid": "aadde1a8-ed7a-4242-bfc0-6dddfd382cf3", - "practices": ["collections", "dict-methods"], + { + "slug": "poker", + "name": "Poker", + "uuid": "dcc0ee26-e384-4bd4-8c4b-613fa0bb8188", + "practices": ["functions", "higher-order-functions"], "prerequisites": [ "basics", - "dicts", + "bools", + "conditionals", + "classes", "lists", "list-methods", - "classes" + "loops", + "numbers" ], "difficulty": 3 }, { - "slug": "circular-buffer", - "name": "Circular Buffer", - "uuid": "77ee3b0e-a4e9-4257-bcfc-ff2c8f1477ab", - "practices": [ - "class-inheritance", - "function-arguments", - "unpacking-and-multiple-assignment", - "user-defined-errors" - ], + "slug": "crypto-square", + "name": "Crypto Square", + "uuid": "e8685468-8006-480f-87c6-6295700def38", + "practices": ["list-comprehensions"], "prerequisites": [ - "basics", - "bools", "conditionals", - "classes", - "dicts", "lists", "list-methods", "loops", "numbers", - "strings" + "strings", + "string-methods" ], "difficulty": 3 }, @@ -1219,6 +1225,27 @@ "prerequisites": ["basics", "numbers", "strings", "classes"], "difficulty": 3 }, + { + "slug": "rectangles", + "name": "Rectangles", + "uuid": "4bebdd8d-a032-4993-85c5-7cc74fc89312", + "practices": ["iteration", "itertools", "sequences"], + "prerequisites": [ + "basics", + "bools", + "classes", + "conditionals", + "list-methods", + "lists", + "loops", + "numbers", + "sets", + "string-methods", + "strings", + "tuples" + ], + "difficulty": 3 + }, { "slug": "simple-linked-list", "name": "Simple Linked List", @@ -1252,24 +1279,6 @@ ], "difficulty": 3 }, - { - "slug": "ocr-numbers", - "name": "OCR Numbers", - "uuid": "98ca48ed-5818-442c-bce1-308c8b3b3b77", - "practices": ["loops"], - "prerequisites": [ - "basics", - "bools", - "conditionals", - "lists", - "list-methods", - "loops", - "numbers", - "strings", - "string-methods" - ], - "difficulty": 3 - }, { "slug": "connect", "name": "Connect", @@ -1291,85 +1300,98 @@ "difficulty": 3 }, { - "slug": "pythagorean-triplet", - "name": "Pythagorean Triplet", - "uuid": "7b53865e-a981-46e0-8e47-6f8e1f3854b3", - "practices": ["sets"], + "slug": "all-your-base", + "name": "All Your Base", + "uuid": "a2ff75f9-8b2c-4c4b-975d-913711def9ab", + "practices": ["comparisons"], "prerequisites": [ "basics", "bools", "conditionals", + "comparisons", "lists", - "list-methods", - "loops", - "numbers", - "sets" + "numbers" ], - "difficulty": 3 + "difficulty": 4 }, { - "slug": "pascals-triangle", - "name": "Pascals Triangle", - "uuid": "e1e1c7d7-c1d9-4027-b90d-fad573182419", - "practices": ["recursion"], + "slug": "minesweeper", + "name": "Minesweeper", + "uuid": "7e768b54-4591-4a30-9ddb-66ca13400ca3", + "practices": ["lists"], "prerequisites": [ "basics", + "bools", "conditionals", "lists", + "loops", "numbers" ], "difficulty": 4 }, { - "slug": "grep", - "name": "Grep", - "uuid": "ecc97fc6-2e72-4325-9b67-b56c83b13a91", - "practices": [], + "slug": "spiral-matrix", + "name": "Spiral Matrix", + "uuid": "b0c7cf95-6470-4c1a-8eaa-6775310926a2", + "practices": ["lists"], "prerequisites": [ "basics", - "bools", "conditionals", + "classes", + "dicts", "lists", "loops", + "numbers", "strings", "string-methods" ], "difficulty": 4 }, { - "slug": "minesweeper", - "name": "Minesweeper", - "uuid": "7e768b54-4591-4a30-9ddb-66ca13400ca3", - "practices": ["lists"], + "slug": "variable-length-quantity", + "name": "Variable Length Quantity", + "uuid": "aa4332bd-fc38-47a4-8bff-e1b660798418", + "practices": ["list-methods"], "prerequisites": [ "basics", "bools", "conditionals", "lists", + "list-methods", "loops", - "numbers" + "numbers", + "strings", + "string-methods" ], "difficulty": 4 }, { - "slug": "meetup", - "name": "Meetup", - "uuid": "a5aff23f-7829-403f-843a-d3312dca31e8", - "practices": [ - "class-composition", - "dict-methods", - "raising-and-handling-errors", - "user-defined-errors" - ], + "slug": "change", + "name": "Change", + "uuid": "889df88a-767d-490f-92c4-552d8ec9de34", + "practices": ["loops"], "prerequisites": [ "basics", "bools", "conditionals", - "classes", - "dicts", "lists", "list-methods", "loops", + "numbers" + ], + "difficulty": 4 + }, + { + "slug": "killer-sudoku-helper", + "name": "Killer Sudoku Helper", + "uuid": "7b16fc93-791b-42a9-8aae-1f78fef2f2f3", + "practices": ["list-comprehensions"], + "prerequisites": [ + "conditionals", + "lists", + "list-methods", + "loops", + "numbers", "strings", "string-methods" ], @@ -1379,7 +1401,7 @@ "slug": "rail-fence-cipher", "name": "Rail Fence Cipher", "uuid": "6434cc19-1ea3-43dd-9580-72267ec76b80", - "practices": ["list-methods"], + "practices": [], "prerequisites": [ "basics", "conditionals", @@ -1393,11 +1415,13 @@ "difficulty": 4 }, { - "slug": "killer-sudoku-helper", - "name": "Killer Sudoku Helper", - "uuid": "7b16fc93-791b-42a9-8aae-1f78fef2f2f3", - "practices": ["list-comprehensions"], + "slug": "palindrome-products", + "name": "Palindrome Products", + "uuid": "fa795dcc-d390-4e98-880c-6e8e638485e3", + "practices": ["functions", "function-arguments"], "prerequisites": [ + "basics", + "bools", "conditionals", "lists", "list-methods", @@ -1415,24 +1439,16 @@ "practices": ["tuples"], "prerequisites": [ "basics", - "strings", - "string-methods", "dicts", - "lists", "list-methods", + "lists", "loops", + "string-methods", + "strings", "tuples" ], "difficulty": 4 }, - { - "slug": "markdown", - "name": "Markdown", - "uuid": "88610b9a-6d3e-4924-a092-6d2f907ed4e2", - "practices": ["regular-expressions", "functions"], - "prerequisites": ["lists", "string-methods"], - "difficulty": 4 - }, { "slug": "food-chain", "name": "Food Chain", @@ -1443,136 +1459,134 @@ "bools", "conditionals", "dicts", - "lists", "list-methods", + "lists", "loops", - "strings", "string-methods", + "strings", "tuples" ], "difficulty": 4 }, { - "slug": "palindrome-products", - "name": "Palindrome Products", - "uuid": "fa795dcc-d390-4e98-880c-6e8e638485e3", - "practices": ["functions", "function-arguments"], + "slug": "scale-generator", + "name": "Scale Generator", + "uuid": "8cd58325-61fc-46fd-85f9-425b4c41f3de", + "practices": ["generators"], "prerequisites": [ "basics", "bools", "conditionals", - "lists", + "dicts", "list-methods", + "lists", "loops", - "numbers", - "strings", - "string-methods" + "string-methods", + "strings" ], "difficulty": 4 }, { - "slug": "linked-list", - "name": "Linked List", - "uuid": "ca7a8b16-e5d5-4211-84f0-2f8e35b4a665", + "slug": "largest-series-product", + "name": "Largest Series Product", + "uuid": "21624a3e-6e43-4c0e-94b0-dee5cdaaf2aa", "practices": [ - "function-arguments", - "iterators", - "none", - "operator-overloading", - "rich-comparisons" + "functions", + "higher-order-functions", + "functional-tools", + "anonymous-functions" ], "prerequisites": [ "basics", - "bools", "conditionals", - "classes", "lists", + "list-methods", "loops", "numbers" ], "difficulty": 4 }, { - "slug": "variable-length-quantity", - "name": "Variable Length Quantity", - "uuid": "aa4332bd-fc38-47a4-8bff-e1b660798418", - "practices": ["list-methods"], - "prerequisites": [ - "basics", - "bools", - "conditionals", - "lists", - "list-methods", - "loops", - "numbers", - "strings", - "string-methods" - ], + "slug": "markdown", + "name": "Markdown", + "uuid": "88610b9a-6d3e-4924-a092-6d2f907ed4e2", + "practices": ["regular-expressions", "functions"], + "prerequisites": ["lists", "loops", "sets", "string-methods"], "difficulty": 4 }, { - "slug": "all-your-base", - "name": "All Your Base", - "uuid": "a2ff75f9-8b2c-4c4b-975d-913711def9ab", - "practices": ["comparisons"], + "slug": "meetup", + "name": "Meetup", + "uuid": "a5aff23f-7829-403f-843a-d3312dca31e8", + "practices": [ + "class-composition", + "dict-methods", + "raising-and-handling-errors", + "user-defined-errors" + ], "prerequisites": [ "basics", "bools", + "classes", "conditionals", - "comparisons", - "numbers" + "dicts", + "dict-methods", + "list-methods", + "lists", + "loops", + "string-methods", + "strings" ], "difficulty": 4 }, { - "slug": "largest-series-product", - "name": "Largest Series Product", - "uuid": "21624a3e-6e43-4c0e-94b0-dee5cdaaf2aa", - "practices": [ - "functions", - "higher-order-functions", - "functional-tools", - "anonymous-functions" - ], + "slug": "pascals-triangle", + "name": "Pascals Triangle", + "uuid": "e1e1c7d7-c1d9-4027-b90d-fad573182419", + "practices": ["recursion"], "prerequisites": [ "basics", "conditionals", + "classes", "lists", - "list-methods", - "loops", "numbers" ], "difficulty": 4 }, { - "slug": "spiral-matrix", - "name": "Spiral Matrix", - "uuid": "b0c7cf95-6470-4c1a-8eaa-6775310926a2", - "practices": ["lists"], + "slug": "grep", + "name": "Grep", + "uuid": "ecc97fc6-2e72-4325-9b67-b56c83b13a91", + "practices": ["with-statement"], "prerequisites": [ "basics", + "bools", "conditionals", "classes", - "dicts", "lists", "loops", - "numbers", "strings", "string-methods" ], "difficulty": 4 }, { - "slug": "change", - "name": "Change", - "uuid": "889df88a-767d-490f-92c4-552d8ec9de34", - "practices": ["loops"], + "slug": "linked-list", + "name": "Linked List", + "uuid": "ca7a8b16-e5d5-4211-84f0-2f8e35b4a665", + "practices": [ + "function-arguments", + "iterators", + "none", + "operator-overloading", + "rich-comparisons" + ], "prerequisites": [ "basics", "bools", "conditionals", + "classes", "lists", - "list-methods", "loops", "numbers" ], @@ -1616,48 +1630,48 @@ "difficulty": 4 }, { - "slug": "go-counting", - "name": "Go Counting", - "uuid": "8a9a437d-c967-4ea3-8ecb-6a9ad4380c03", + "slug": "hangman", + "name": "Hangman", + "uuid": "adad6be5-855d-4d61-b14a-22e468ba5b44", "practices": [], "prerequisites": [ "basics", "bools", "conditionals", "classes", + "dicts", "lists", "list-methods", "loops", - "sets", - "tuples" + "numbers", + "strings", + "string-methods" ], "difficulty": 4 }, { - "slug": "hangman", - "name": "Hangman", - "uuid": "adad6be5-855d-4d61-b14a-22e468ba5b44", + "slug": "go-counting", + "name": "Go Counting", + "uuid": "8a9a437d-c967-4ea3-8ecb-6a9ad4380c03", "practices": [], "prerequisites": [ "basics", "bools", "conditionals", "classes", - "dicts", "lists", "list-methods", "loops", - "numbers", - "strings", - "string-methods" + "sets", + "tuples" ], "difficulty": 4 }, { - "slug": "scale-generator", - "name": "Scale Generator", - "uuid": "8cd58325-61fc-46fd-85f9-425b4c41f3de", - "practices": ["generators"], + "slug": "forth", + "name": "Forth", + "uuid": "14e1dfe3-a45c-40c1-bf61-2e4f0cca5579", + "practices": ["dicts"], "prerequisites": [ "basics", "bools", @@ -1666,25 +1680,27 @@ "lists", "list-methods", "loops", + "numbers", "strings", - "string-methods" + "tuples" ], - "difficulty": 4 + "difficulty": 5 }, { - "slug": "knapsack", - "name": "Knapsack", - "uuid": "b0301d0b-d97a-4043-bd82-ba1edf8c1b16", - "practices": ["itertools", "list-comprehensions"], + "slug": "binary-search-tree", + "name": "Binary Search Tree", + "uuid": "df7cd9b9-283a-4466-accf-98c4a7609450", + "practices": ["classes"], "prerequisites": [ "basics", "bools", "conditionals", - "dicts", + "classes", "lists", "list-methods", "loops", - "strings" + "strings", + "string-methods" ], "difficulty": 5 }, @@ -1706,15 +1722,15 @@ "difficulty": 5 }, { - "slug": "forth", - "name": "Forth", - "uuid": "14e1dfe3-a45c-40c1-bf61-2e4f0cca5579", - "practices": ["dicts"], + "slug": "bowling", + "name": "Bowling", + "uuid": "ca970fee-71b4-41e1-a5c3-b23bf574eb33", + "practices": [], "prerequisites": [ "basics", "bools", "conditionals", - "dicts", + "classes", "lists", "list-methods", "loops", @@ -1725,45 +1741,19 @@ "difficulty": 5 }, { - "slug": "custom-set", - "name": "Custom Set", - "uuid": "23a567b5-c184-4e65-9216-df7caba00d75", - "practices": [ - "class-inheritance", - "operator-overloading", - "rich-comparisons" - ], + "slug": "knapsack", + "name": "Knapsack", + "uuid": "b0301d0b-d97a-4043-bd82-ba1edf8c1b16", + "practices": ["itertools", "list-comprehensions"], "prerequisites": [ "basics", "bools", "conditionals", - "classes", - "lists", + "dicts", "list-methods", - "loops", - "numbers", - "sets", - "strings", - "string-methods" - ], - "difficulty": 5 - }, - { - "slug": "bowling", - "name": "Bowling", - "uuid": "ca970fee-71b4-41e1-a5c3-b23bf574eb33", - "practices": [], - "prerequisites": [ - "basics", - "bools", - "conditionals", - "classes", "lists", - "list-methods", "loops", - "numbers", - "strings", - "tuples" + "strings" ], "difficulty": 5 }, @@ -1793,33 +1783,39 @@ "difficulty": 5 }, { - "slug": "zebra-puzzle", - "name": "Zebra Puzzle", - "uuid": "7e1d90d5-dbc9-47e0-8e26-c3ff83b73c2b", - "practices": ["itertools"], + "slug": "custom-set", + "name": "Custom Set", + "uuid": "23a567b5-c184-4e65-9216-df7caba00d75", + "practices": [ + "class-inheritance", + "operator-overloading", + "rich-comparisons" + ], "prerequisites": [ "basics", "bools", "conditionals", - "dicts", + "classes", "lists", "list-methods", "loops", + "numbers", + "sets", "strings", "string-methods" ], "difficulty": 5 }, { - "slug": "binary-search-tree", - "name": "Binary Search Tree", - "uuid": "df7cd9b9-283a-4466-accf-98c4a7609450", - "practices": ["classes"], + "slug": "zebra-puzzle", + "name": "Zebra Puzzle", + "uuid": "7e1d90d5-dbc9-47e0-8e26-c3ff83b73c2b", + "practices": ["itertools"], "prerequisites": [ "basics", "bools", "conditionals", - "classes", + "dicts", "lists", "list-methods", "loops", @@ -1836,6 +1832,7 @@ "prerequisites": [ "basics", "bools", + "classes", "conditionals", "lists", "list-methods", @@ -1850,12 +1847,7 @@ "slug": "word-search", "name": "Word Search", "uuid": "dc2917d5-aaa9-43d9-b9f4-a32919fdbe18", - "practices": [ - "iteration", - "operator-overloading", - "rich-comparisons", - "string-formatting" - ], + "practices": ["iteration"], "prerequisites": [ "basics", "bools", @@ -1871,20 +1863,6 @@ ], "difficulty": 6 }, - { - "slug": "bank-account", - "name": "Bank Account", - "uuid": "83a3ff95-c043-401c-bc2c-547d52344b02", - "practices": ["enums", "raising-and-handling-errors"], - "prerequisites": [ - "basics", - "bools", - "conditionals", - "classes", - "loops" - ], - "difficulty": 6 - }, { "slug": "alphametics", "name": "Alphametics", @@ -1893,19 +1871,34 @@ "prerequisites": [ "basics", "bools", + "classes", "conditionals", "dicts", - "lists", "list-methods", + "lists", "loops", "numbers", "sets", - "strings", "string-methods", + "strings", "tuples" ], "difficulty": 6 }, + { + "slug": "bank-account", + "name": "Bank Account", + "uuid": "83a3ff95-c043-401c-bc2c-547d52344b02", + "practices": ["enums", "raising-and-handling-errors"], + "prerequisites": [ + "basics", + "bools", + "conditionals", + "classes", + "loops" + ], + "difficulty": 6 + }, { "slug": "react", "name": "React", @@ -1954,6 +1947,7 @@ "prerequisites": [ "basics", "bools", + "classes", "conditionals", "dicts", "lists", @@ -1965,30 +1959,6 @@ ], "difficulty": 6 }, - { - "slug": "book-store", - "name": "Book Store", - "uuid": "4899b2ef-675f-4d14-b68a-1a457de91276", - "practices": [ - "collections", - "functools", - "generator-expressions", - "other-comprehensions", - "sets" - ], - "prerequisites": [ - "basics", - "conditionals", - "dicts", - "lists", - "list-methods", - "loops", - "tuples", - "sets", - "numbers" - ], - "difficulty": 7 - }, { "slug": "dominoes", "name": "Dominoes", @@ -2029,6 +1999,31 @@ ], "difficulty": 7 }, + { + "slug": "book-store", + "name": "Book Store", + "uuid": "4899b2ef-675f-4d14-b68a-1a457de91276", + "practices": [ + "collections", + "functools", + "generator-expressions", + "other-comprehensions", + "sets" + ], + "prerequisites": [ + "basics", + "classes", + "conditionals", + "dicts", + "list-methods", + "lists", + "loops", + "numbers", + "sets", + "tuples" + ], + "difficulty": 7 + }, { "slug": "sgf-parsing", "name": "SGF Parsing", From b53d6ace70fb1cb058e694fb811df0a2b4e7b252 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 16 Aug 2023 09:41:10 +0200 Subject: [PATCH 513/826] Use `assets.exercism.org` subdomain (#3488) As part of moving various parts of our image hosting behind a CDN, this commit updates the involved urls we automatically found in this repository. --- .../practice/killer-sudoku-helper/.docs/instructions.md | 6 +++--- exercises/practice/simple-linked-list/.docs/hints.md | 2 +- .../simple-linked-list/.docs/instructions.append.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md index 2347ab262c..8bb05a3a77 100644 --- a/exercises/practice/killer-sudoku-helper/.docs/instructions.md +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -56,8 +56,8 @@ The screenshots above have been generated using [F-Puzzles.com](https://www.f-pu [sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/ [killer-guide]: https://masteringsudoku.com/killer-sudoku/ -[one-solution-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example1.png -[four-solutions-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example2.png -[not-possible-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example3.png +[one-solution-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example1.png +[four-solutions-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example2.png +[not-possible-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example3.png [clover-puzzle]: https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R [goodliffe-video]: https://youtu.be/c_NjEbFEeW0?t=1180 diff --git a/exercises/practice/simple-linked-list/.docs/hints.md b/exercises/practice/simple-linked-list/.docs/hints.md index 1ecef83ea2..da373540ad 100644 --- a/exercises/practice/simple-linked-list/.docs/hints.md +++ b/exercises/practice/simple-linked-list/.docs/hints.md @@ -5,7 +5,7 @@ - This challenge is about creating a [_stack_][Baeldung: The Stack Data Structure] using a [singly linked list][singly linked list]. - Unlike stacks underpinned with `lists`, `collections.deque`, or `queue.LifoQueue`, we ask you create custom `Node` and `LinkedList` [`classes`][classes tutorial] to store and link elements. -![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". This pattern continues until Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/tracks/python/simple-linked-list/linked-list.svg) +![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". This pattern continues until Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://assets.exercism.org/images/tracks/python/simple-linked-list/linked-list.svg) - [Real Python: Linked Lists][Real Python Linked Lists], [Towards Data Science: Demystifying the Linked List][towards data science demystifying the linked list], and [ADS Stack in Python][Koder Dojo Coding an ADS Stack in Python] can be helpful to review for details on implementation. - Your `LinkedList` should accept a `list` argument to its _constructor_, but should not use a `list` to store nodes or elements. diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/instructions.append.md index 41db11df96..4a58dbeb94 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.append.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.append.md @@ -4,12 +4,12 @@ While `stacks` and `queues` can be implemented using `lists`, `collections.deque`, `queue.LifoQueue`, and `multiprocessing.Queue`, this exercise expects a ["Last in, First Out" (`LIFO`) stack][Baeldung: The Stack Data Structure] (_interactive example [here][LIFO Stack]_) using a _custom-made_ [singly linked list][singly linked list]. -![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". This pattern continues until Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/tracks/python/simple-linked-list/linked-list.svg) +![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". This pattern continues until Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://assets.exercism.org/images/tracks/python/simple-linked-list/linked-list.svg) This should not be confused with a [`LIFO` stack using a dynamic array or list][LIFO Stack Array], which may use a `list` underneath. Dynamic array based `stacks` have a different `head` position and different time complexity (Big-O) and memory footprint. -![Diagram representing a stack implemented with an array/dynamic array. A box with a dashed border named New_Node is to the far right-hand side, with two dotted arrow lines pointing left-ward. New_Node reads "(becomes head) - New_Node". The top dotted arrow line is labeled "append" and points to Node_6, above and to the left. Node_6 reads "(current) head - Node_6". The bottom dotted arrow line is labeled "pop" and points to a box with a dotted outline that reads "gets removed on pop()". Node_6 has a solid arrow that points leftward to Node_5. Node_5 has a solid arrow pointing leftward to Node_4. This pattern continues until Node_1, which reads "(current) tail - Node_1".](https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/tracks/python/simple-linked-list/linked_list_array.svg) +![Diagram representing a stack implemented with an array/dynamic array. A box with a dashed border named New_Node is to the far right-hand side, with two dotted arrow lines pointing left-ward. New_Node reads "(becomes head) - New_Node". The top dotted arrow line is labeled "append" and points to Node_6, above and to the left. Node_6 reads "(current) head - Node_6". The bottom dotted arrow line is labeled "pop" and points to a box with a dotted outline that reads "gets removed on pop()". Node_6 has a solid arrow that points leftward to Node_5. Node_5 has a solid arrow pointing leftward to Node_4. This pattern continues until Node_1, which reads "(current) tail - Node_1".](https://assets.exercism.org/images/tracks/python/simple-linked-list/linked_list_array.svg) See these two Stack Overflow questions for some considerations: [Array-Based vs List-Based Stacks and Queues][Stack Overflow: Array-Based vs List-Based Stacks and Queues] and [Differences between Array Stack, Linked Stack, and Stack][Stack Overflow: What is the difference between Array Stack, Linked Stack, and Stack]. From 1d8b11a7eaa13ebfae35a9f9e195baa72f268609 Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Fri, 18 Aug 2023 20:20:15 +0100 Subject: [PATCH 514/826] little-sisters-vocab: Update stub to match instructions (#3489) Tiny tweak based on https://forum.exercism.org/t/little-sisters-vocabulatry-discrepency-between-test-and-docstring-for-adjective-to-verb/6949/3 --- exercises/concept/little-sisters-vocab/strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/little-sisters-vocab/strings.py b/exercises/concept/little-sisters-vocab/strings.py index 8c4fff0b89..39ae7bb80c 100644 --- a/exercises/concept/little-sisters-vocab/strings.py +++ b/exercises/concept/little-sisters-vocab/strings.py @@ -48,7 +48,7 @@ def adjective_to_verb(sentence, index): :param index: int - index of the word to remove and transform. :return: str - word that changes the extracted adjective to a verb. - For example, ("It got dark as the sun set", 2) becomes "darken". + For example, ("It got dark as the sun set.", 2) becomes "darken". """ pass From 06ec554166a693ab90facc900b755bbde063db9b Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Sun, 20 Aug 2023 01:49:31 +0530 Subject: [PATCH 515/826] Fix link rendering (#3490) * typo * another typo --- exercises/practice/atbash-cipher/.approaches/introduction.md | 4 ++-- exercises/practice/robot-name/.approaches/introduction.md | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/exercises/practice/atbash-cipher/.approaches/introduction.md b/exercises/practice/atbash-cipher/.approaches/introduction.md index dff79f96c6..e141c8251b 100644 --- a/exercises/practice/atbash-cipher/.approaches/introduction.md +++ b/exercises/practice/atbash-cipher/.approaches/introduction.md @@ -10,7 +10,7 @@ For decoding, it's similar - clean up (which automatically joins the chunks) and ## Approach: separate functions We use `str.maketrans` to create the encoding. -In `encode`, we use a [generator expression][generator expression] in `str.join`. +In `encode`, we use a [generator expression][generator-expression] in `str.join`. ```python from string import ascii_lowercase ENCODING = str.maketrans(ascii_lowercase, ascii_lowercase[::-1]) @@ -43,4 +43,4 @@ For more detail, [read here][approach-mono-function]. [approach-separate-functions]: https://exercism.org/tracks/python/exercises/atbash-cipher/approaches/separate-functions [approach-mono-function]: https://exercism.org/tracks/python/exercises/atbash-cipher/approaches/mono-function -[generator expression]: https://www.programiz.com/python-programming/generator +[generator-expression]: https://www.programiz.com/python-programming/generator diff --git a/exercises/practice/robot-name/.approaches/introduction.md b/exercises/practice/robot-name/.approaches/introduction.md index 9dc810ed5e..c4b6738380 100644 --- a/exercises/practice/robot-name/.approaches/introduction.md +++ b/exercises/practice/robot-name/.approaches/introduction.md @@ -56,4 +56,7 @@ class Robot: self.reset() ``` -For more detail and different ways to implement this, [read here][approach-name-on-the-fly]. \ No newline at end of file +For more detail and different ways to implement this, [read here][approach-name-on-the-fly]. + +[approach-name-on-the-fly]: https://exercism.org/tracks/python/exercises/robot-name/approaches/name-on-the-fly +[approach-mass-name-generation]: https://exercism.org/tracks/python/exercises/robot-name/approaches/mass-name-generation From 108222bb601aaae120587ba149d7a9734b765861 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 22 Aug 2023 12:21:33 -0700 Subject: [PATCH 516/826] [Simple Linked List]: Swapped out Paywalled Links & Tightened Text for Instruction Append (#3491) * Swapped out paywalled links and tighened text and images. * Further small link edits and widgets. --- .../.docs/instructions.append.md | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/instructions.append.md index 4a58dbeb94..888e675a84 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.append.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.append.md @@ -2,46 +2,51 @@ ## How this Exercise is Structured in Python -While `stacks` and `queues` can be implemented using `lists`, `collections.deque`, `queue.LifoQueue`, and `multiprocessing.Queue`, this exercise expects a ["Last in, First Out" (`LIFO`) stack][Baeldung: The Stack Data Structure] (_interactive example [here][LIFO Stack]_) using a _custom-made_ [singly linked list][singly linked list]. +While `stacks` and `queues` can be implemented using `lists`, `collections.deque`, `queue.LifoQueue`, and `multiprocessing.Queue`, this exercise expects a ["Last in, First Out" (`LIFO`) stack][baeldung: the stack data structure] using a _custom-made_ [singly linked list][singly linked list]: + +
![Diagram representing a stack implemented with a linked list. A circle with a dashed border named New_Node is to the far left-hand side, with two dotted arrow lines pointing right-ward. New_Node reads "(becomes head) - New_Node - next = node_6". The top dotted arrow line is labeled "push" and points to Node_6, above and to the right. Node_6 reads "(current) head - Node_6 - next = node_5". The bottom dotted arrow line is labeled "pop" and points to a box that reads "gets removed on pop()". Node_6 has a solid arrow that points rightward to Node_5, which reads "Node_5 - next = node_4". Node_5 has a solid arrow pointing rightward to Node_4, which reads "Node_4 - next = node_3". This pattern continues until Node_1, which reads "(current) tail - Node_1 - next = None". Node_1 has a dotted arrow pointing rightward to a node that says "None".](https://assets.exercism.org/images/tracks/python/simple-linked-list/linked-list.svg) -This should not be confused with a [`LIFO` stack using a dynamic array or list][LIFO Stack Array], which may use a `list` underneath. +
+ +This should not be confused with a [`LIFO` stack using a dynamic array or list][lifo stack array], which may use a `list`, `queue`, or `array` underneath. Dynamic array based `stacks` have a different `head` position and different time complexity (Big-O) and memory footprint. +
+ ![Diagram representing a stack implemented with an array/dynamic array. A box with a dashed border named New_Node is to the far right-hand side, with two dotted arrow lines pointing left-ward. New_Node reads "(becomes head) - New_Node". The top dotted arrow line is labeled "append" and points to Node_6, above and to the left. Node_6 reads "(current) head - Node_6". The bottom dotted arrow line is labeled "pop" and points to a box with a dotted outline that reads "gets removed on pop()". Node_6 has a solid arrow that points leftward to Node_5. Node_5 has a solid arrow pointing leftward to Node_4. This pattern continues until Node_1, which reads "(current) tail - Node_1".](https://assets.exercism.org/images/tracks/python/simple-linked-list/linked_list_array.svg) -See these two Stack Overflow questions for some considerations: [Array-Based vs List-Based Stacks and Queues][Stack Overflow: Array-Based vs List-Based Stacks and Queues] and [Differences between Array Stack, Linked Stack, and Stack][Stack Overflow: What is the difference between Array Stack, Linked Stack, and Stack]. +
+See these two Stack Overflow questions for some considerations: [Array-Based vs List-Based Stacks and Queues][stack overflow: array-based vs list-based stacks and queues] and [Differences between Array Stack, Linked Stack, and Stack][stack overflow: what is the difference between array stack, linked stack, and stack]. For more details on linked lists, `LIFO` stacks, and other abstract data types (`ADT`) in Python: +- [Baeldung: Linked-list Data Structures][baeldung linked lists] (_covers multiple implementations_) +- [Geeks for Geeks: Stack with Linked List][geeks for geeks stack with linked list] +- [Mosh on Abstract Data Structures][mosh data structures in python] (_covers many `ADT`s, not just linked lists_) -- [Real Python: Linked Lists][Real Python Linked Lists] (_covers multiple implementations_) -- [Towards Data Science: Demystifying the Linked List][towards data science demystifying the linked list] -- [Towards Data Science: Singly Linked Lists][singly linked list] -- [Geeks for Geeks: Stack with Linked List][Geeks for Geeks Stack with Linked List] -- [Scaler Topics: Stacks in Python][Scaler Topics Stack in Python] -- [Mosh on Abstract Data Structures][Mosh Data Structures in Python] (_covers many `ADT`s, not just linked lists_) +
+## Classes in Python The "canonical" implementation of a linked list in Python usually requires one or more `classes`. -For a good introduction to `classes`, see the [Class section of the Official Python Tutorial][classes tutorial]. - +For a good introduction to `classes`, see the [concept:python/classes]() and companion exercise [exercise:python/ellens-alien-game](), or [Class section of the Official Python Tutorial][classes tutorial].
## Special Methods in Python -The tests for this exercise will also be calling `len()` on your `LinkedList`. +The tests for this exercise will be calling `len()` on your `LinkedList`. In order for `len()` to work, you will need to create a `__len__` special method. -For details on implementing special or "dunder" methods in Python, see [Python Docs: Basic Object Customization][basic customization] and [Python Docs: object.__len__(self)][__len__]. +For details on implementing special or "dunder" methods in Python, see [Python Docs: Basic Object Customization][basic customization] and [Python Docs: object.**len**(self)][__len__].
## Building an Iterator To support looping through or reversing your `LinkedList`, you will need to implement the `__iter__` special method. -See [implementing an iterator for a class.](https://docs.python.org/3/tutorial/classes.html#iterators) for implementation details. +See [implementing an iterator for a class.][custom iterators] for implementation details.
@@ -72,28 +77,25 @@ class EmptyListException(Exception): """ def __init__(self, message): self.message = message - + # raising an EmptyListException raise EmptyListException("The list is empty.") ``` - -[Baeldung: The Stack Data Structure]: https://www.baeldung.com/cs/stack-data-structure -[Geeks for Geeks Stack with Linked List]: https://www.geeksforgeeks.org/implement-a-stack-using-singly-linked-list/ -[LIFO Stack Array]: https://courses.cs.washington.edu/courses/cse373/16wi/Hashing/visualization/StackArray.html -[LIFO Stack]: https://courses.cs.washington.edu/courses/cse373/16wi/Hashing/visualization/StackLL.html -[Mosh Data Structures in Python]: https://programmingwithmosh.com/data-structures/data-structures-in-python-stacks-queues-linked-lists-trees/ -[Real Python Linked Lists]: https://realpython.com/linked-lists-python/ -[Scaler Topics Stack in Python]: https://www.scaler.com/topics/stack-in-python/ -[Stack Overflow: Array-Based vs List-Based Stacks and Queues]: https://stackoverflow.com/questions/7477181/array-based-vs-list-based-stacks-and-queues?rq=1 -[Stack Overflow: What is the difference between Array Stack, Linked Stack, and Stack]: https://stackoverflow.com/questions/22995753/what-is-the-difference-between-array-stack-linked-stack-and-stack [__len__]: https://docs.python.org/3/reference/datamodel.html#object.__len__ +[baeldung linked lists]: https://www.baeldung.com/cs/linked-list-data-structure +[baeldung: the stack data structure]: https://www.baeldung.com/cs/stack-data-structure [basic customization]: https://docs.python.org/3/reference/datamodel.html#basic-customization [built-in errors]: https://docs.python.org/3/library/exceptions.html#base-classes [classes tutorial]: https://docs.python.org/3/tutorial/classes.html#tut-classes +[custom iterators]: https://docs.python.org/3/tutorial/classes.html#iterators [customize errors]: https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions [exception base class]: https://docs.python.org/3/library/exceptions.html#Exception +[geeks for geeks stack with linked list]: https://www.geeksforgeeks.org/implement-a-stack-using-singly-linked-list/ +[lifo stack array]: https://www.scaler.com/topics/stack-in-python/ +[mosh data structures in python]: https://programmingwithmosh.com/data-structures/data-structures-in-python-stacks-queues-linked-lists-trees/ [raise statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement [raising exceptions]: https://docs.python.org/3/tutorial/errors.html#raising-exceptions -[singly linked list]: https://towardsdatascience.com/python-linked-lists-c3622205da81 -[towards data science demystifying the linked list]: https://towardsdatascience.com/demystifying-linked-list-258dfb9f2176 +[singly linked list]: https://blog.boot.dev/computer-science/building-a-linked-list-in-python-with-examples/ +[stack overflow: array-based vs list-based stacks and queues]: https://stackoverflow.com/questions/7477181/array-based-vs-list-based-stacks-and-queues?rq=1 +[stack overflow: what is the difference between array stack, linked stack, and stack]: https://stackoverflow.com/questions/22995753/what-is-the-difference-between-array-stack-linked-stack-and-stack From 1e71b8a00c8b34c251d785f0a10843efc5234994 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Fri, 25 Aug 2023 18:20:16 +0530 Subject: [PATCH 517/826] another typo (#3493) --- exercises/practice/atbash-cipher/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/atbash-cipher/.approaches/introduction.md b/exercises/practice/atbash-cipher/.approaches/introduction.md index e141c8251b..6c7180eff9 100644 --- a/exercises/practice/atbash-cipher/.approaches/introduction.md +++ b/exercises/practice/atbash-cipher/.approaches/introduction.md @@ -22,7 +22,7 @@ def encode(text: str): def decode(text: str): return "".join(chr.lower() for chr in text if chr.isalnum()).translate(ENCODING) ``` -Read more on this [approach here][approach-seperate-functions]. +Read more on this [approach here][approach-separate-functions]. ## Approach: mono-function Notice that there the majority of the code is repetitive? From c0c2f677f416fe5153cf390a2f2a074e51f551af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:28:19 -0700 Subject: [PATCH 518/826] Bump actions/checkout from 3 to 4 (#3498) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 01ddab0cac..8a8803bfb9 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 7bddcd077c..dfd6c59c51 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index ecfc2a10fc..b3668593b8 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run test-runner run: docker-compose run test-runner From 3386a897a3d60248a462327dd765bbaba66b4633 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 7 Sep 2023 15:08:41 -0700 Subject: [PATCH 519/826] Synced problem descriptions to problem specs. (#3499) --- exercises/practice/darts/.docs/instructions.md | 8 ++++++++ exercises/practice/perfect-numbers/.docs/instructions.md | 5 ++--- .../practice/resistor-color-trio/.docs/instructions.md | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/exercises/practice/darts/.docs/instructions.md b/exercises/practice/darts/.docs/instructions.md index 70f0e53da7..5e57a860af 100644 --- a/exercises/practice/darts/.docs/instructions.md +++ b/exercises/practice/darts/.docs/instructions.md @@ -6,6 +6,8 @@ Write a function that returns the earned points in a single toss of a Darts game In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands: +![Our dart scoreboard with values from a complete miss to a bullseye](https://assets.exercism.org/images/exercises/darts/darts-scoreboard.svg) + - If the dart lands outside the target, player earns no points (0 points). - If the dart lands in the outer circle of the target, player earns 1 point. - If the dart lands in the middle circle of the target, player earns 5 points. @@ -16,8 +18,14 @@ Of course, they are all centered at the same point — that is, the circles are Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point. +## Credit + +The scoreboard image was created by [habere-et-dispertire][habere-et-dispertire] using [Inkscape][inkscape]. + [darts]: https://en.wikipedia.org/wiki/Darts [darts-target]: https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg [concentric]: https://mathworld.wolfram.com/ConcentricCircles.html [cartesian-coordinates]: https://www.mathsisfun.com/data/cartesian-coordinates.html [real-numbers]: https://www.mathsisfun.com/numbers/real-numbers.html +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[inkscape]: https://en.wikipedia.org/wiki/Inkscape diff --git a/exercises/practice/perfect-numbers/.docs/instructions.md b/exercises/practice/perfect-numbers/.docs/instructions.md index 0dae8867ff..689a73c00d 100644 --- a/exercises/practice/perfect-numbers/.docs/instructions.md +++ b/exercises/practice/perfect-numbers/.docs/instructions.md @@ -1,11 +1,10 @@ # Instructions -Determine if a number is perfect, abundant, or deficient based on -Nicomachus' (60 - 120 CE) classification scheme for positive integers. +Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum][aliquot-sum]. The aliquot sum is defined as the sum of the factors of a number not including the number itself. -For example, the aliquot sum of 15 is (1 + 3 + 5) = 9 +For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`. - **Perfect**: aliquot sum = number - 6 is a perfect number because (1 + 2 + 3) = 6 diff --git a/exercises/practice/resistor-color-trio/.docs/instructions.md b/exercises/practice/resistor-color-trio/.docs/instructions.md index 4ad2aede37..59d22783b9 100644 --- a/exercises/practice/resistor-color-trio/.docs/instructions.md +++ b/exercises/practice/resistor-color-trio/.docs/instructions.md @@ -23,7 +23,7 @@ For this exercise, you need to know only three things about them: - Grey: 8 - White: 9 -In `resistor-color duo` you decoded the first two colors. +In Resistor Color Duo you decoded the first two colors. For instance: orange-orange got the main value `33`. The third color stands for how many zeros need to be added to the main value. The main value plus the zeros gives us a value in ohms. From 49cc1ecc0daa8cfdf7294a51072de565ff4a967a Mon Sep 17 00:00:00 2001 From: glaxxie <86179463+glaxxie@users.noreply.github.com> Date: Fri, 8 Sep 2023 00:35:45 -0500 Subject: [PATCH 520/826] Update instructions.md (#3500) Fix some small typos and one example --- .../practice/resistor-color-expert/.docs/instructions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/resistor-color-expert/.docs/instructions.md b/exercises/practice/resistor-color-expert/.docs/instructions.md index d76c567b5d..7a110832c8 100644 --- a/exercises/practice/resistor-color-expert/.docs/instructions.md +++ b/exercises/practice/resistor-color-expert/.docs/instructions.md @@ -61,19 +61,19 @@ This exercise is about translating the resistor band colors into a label: "... ohms ...%" -So an input of "orange", "orange", "black, green" should return: +So an input of "orange", "orange", "black", "green" should return: "33 ohms ±0.5%" When there are more than a thousand ohms, we say "kiloohms". That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. -So an input of "orange", "orange", "orange", grey should return: +So an input of "orange", "orange", "orange", "grey" should return: "33 kiloohms ±0.05%" When there are more than a million ohms, we say "megaohms". -So an input of "orange", "orange", "orange", "red" should return: +So an input of "orange", "orange", "blue", "red" should return: "33 megaohms ±2%" From 418e5d2a58a77da0c02bda4dfd19607898e5c811 Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Mon, 11 Sep 2023 20:22:10 +0100 Subject: [PATCH 521/826] [meltdown-mitigation] Be super explicit (#3501) * [meltdown-mitigation] Be super explicit It seems a few people get stuck on this (maybe because the sentence is quite long and for non-native speakers it's tough to parse - e.g. "plus or minus 10%" could feel confusing maybe?) so I thought maybe we become SUPER explicit. * Update exercises/concept/meltdown-mitigation/.docs/instructions.md --------- Co-authored-by: BethanyG --- exercises/concept/meltdown-mitigation/.docs/instructions.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/exercises/concept/meltdown-mitigation/.docs/instructions.md b/exercises/concept/meltdown-mitigation/.docs/instructions.md index 1b8af330d3..3d6d96d0cd 100644 --- a/exercises/concept/meltdown-mitigation/.docs/instructions.md +++ b/exercises/concept/meltdown-mitigation/.docs/instructions.md @@ -61,9 +61,7 @@ Implement the function called `fail_safe()`, which takes 3 parameters: `temperat - If `temperature * neutrons_produced_per_second` < 90% of `threshold`, output a status code of 'LOW' indicating that control rods must be removed to produce power. -- If `temperature * neutrons_produced_per_second` are within plus or minus 10% of the `threshold` - the reactor is in _criticality_ and the status code of 'NORMAL' should be output, indicating that the - reactor is in optimum condition and control rods are in an ideal position. +- If the value `temperature * neutrons_produced_per_second` is within 10% of the `threshold` (so either 0-10% less than the threshold, at the threshold, or 0-10% greater than the threshold), the reactor is in _criticality_ and the status code of 'NORMAL' should be output, indicating that the reactor is in optimum condition and control rods are in an ideal position. - If `temperature * neutrons_produced_per_second` is not in the above-stated ranges, the reactor is going into meltdown and a status code of 'DANGER' must be passed to immediately shut down the reactor. From adc465969cdaa0947690271286d4108508148f2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 18:04:46 -0700 Subject: [PATCH 522/826] Bump exercism/pr-commenter-action from 1.4.0 to 1.5.0 (#3503) Bumps [exercism/pr-commenter-action](https://github.com/exercism/pr-commenter-action) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/exercism/pr-commenter-action/releases) - [Changelog](https://github.com/exercism/pr-commenter-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/exercism/pr-commenter-action/compare/v1.4.0...v1.5.0) --- updated-dependencies: - dependency-name: exercism/pr-commenter-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr-commenter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index 13c0b62d44..0bf1662006 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -6,7 +6,7 @@ jobs: pr-comment: runs-on: ubuntu-latest steps: - - uses: exercism/pr-commenter-action@v1.4.0 + - uses: exercism/pr-commenter-action@v1.5.0 with: github-token: "${{ github.token }}" config-file: ".github/pr-commenter.yml" \ No newline at end of file From dc28ef222481224bf4e94310a76048414184664e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 22 Sep 2023 01:20:13 -0700 Subject: [PATCH 523/826] [Problem Spec Sync] Practice Exercise Instruction Sync (#3507) * Synced exercise insructions with problem specifications. * Delete exercises/practice/raindrops/.articles/Progression Revised.md --> File added by mistake. Still needs additions and revisions before committing. --- .../practice/acronym/.docs/instructions.md | 10 ++-- .../affine-cipher/.docs/instructions.md | 4 +- .../all-your-base/.docs/instructions.md | 8 ++-- .../practice/allergies/.docs/instructions.md | 2 +- .../armstrong-numbers/.docs/instructions.md | 4 +- .../binary-search/.docs/instructions.md | 6 +-- .../practice/book-store/.docs/instructions.md | 8 ++-- .../practice/bowling/.docs/instructions.md | 6 +-- .../circular-buffer/.docs/instructions.md | 48 ++++++++++++------- .../practice/dot-dsl/.docs/instructions.md | 2 +- exercises/practice/etl/.docs/instructions.md | 4 +- .../practice/gigasecond/.docs/introduction.md | 4 +- .../practice/isogram/.docs/instructions.md | 2 +- .../practice/knapsack/.docs/instructions.md | 5 +- .../linked-list/.docs/instructions.md | 4 +- .../practice/list-ops/.docs/instructions.md | 16 +++---- .../practice/pangram/.docs/introduction.md | 4 +- .../phone-number/.docs/instructions.md | 4 +- .../protein-translation/.docs/instructions.md | 20 ++++---- .../rational-numbers/.docs/instructions.md | 4 +- .../practice/rest-api/.docs/instructions.md | 12 ++--- .../rna-transcription/.docs/instructions.md | 4 +- .../rna-transcription/.docs/introduction.md | 4 +- .../rotational-cipher/.docs/instructions.md | 4 +- .../scale-generator/.docs/instructions.md | 20 ++++---- .../secret-handshake/.docs/instructions.md | 4 +- .../practice/series/.docs/instructions.md | 4 +- .../sgf-parsing/.docs/instructions.md | 2 +- .../practice/sieve/.docs/instructions.md | 4 +- .../simple-linked-list/.docs/instructions.md | 4 +- .../practice/wordy/.docs/instructions.md | 2 +- .../practice/yacht/.docs/instructions.md | 32 ++++++------- 32 files changed, 140 insertions(+), 121 deletions(-) diff --git a/exercises/practice/acronym/.docs/instructions.md b/exercises/practice/acronym/.docs/instructions.md index c62fc3e85f..133bd2cbb7 100644 --- a/exercises/practice/acronym/.docs/instructions.md +++ b/exercises/practice/acronym/.docs/instructions.md @@ -10,8 +10,8 @@ Punctuation is handled as follows: hyphens are word separators (like whitespace) For example: -|Input|Output| -|-|-| -|As Soon As Possible|ASAP| -|Liquid-crystal display|LCD| -|Thank George It's Friday!|TGIF| +| Input | Output | +| ------------------------- | ------ | +| As Soon As Possible | ASAP | +| Liquid-crystal display | LCD | +| Thank George It's Friday! | TGIF | diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 2ad6d15215..26ce153426 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -6,7 +6,7 @@ The affine cipher is a type of monoalphabetic substitution cipher. Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value. Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the atbash cipher, because it has many more keys. -[//]: # ( monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic ) +[//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic " ## Encryption @@ -23,7 +23,7 @@ Where: For the Roman alphabet `m` is `26`. - `a` and `b` are integers which make the encryption key -Values `a` and `m` must be *coprime* (or, *relatively prime*) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). +Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). In case `a` is not coprime to `m`, your program should indicate that this is an error. Otherwise it should encrypt or decrypt with the provided key. diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md index d5a2cde652..4602b5cfad 100644 --- a/exercises/practice/all-your-base/.docs/instructions.md +++ b/exercises/practice/all-your-base/.docs/instructions.md @@ -14,20 +14,20 @@ Given a number in base **a**, represented as a sequence of digits, convert it to In positional notation, a number in base **b** can be understood as a linear combination of powers of **b**. -The number 42, *in base 10*, means: +The number 42, _in base 10_, means: `(4 * 10^1) + (2 * 10^0)` -The number 101010, *in base 2*, means: +The number 101010, _in base 2_, means: `(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)` -The number 1120, *in base 3*, means: +The number 1120, _in base 3_, means: `(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)` I think you got the idea! -*Yes. Those three numbers above are exactly the same. Congratulations!* +_Yes. Those three numbers above are exactly the same. Congratulations!_ [positional-notation]: https://en.wikipedia.org/wiki/Positional_notation diff --git a/exercises/practice/allergies/.docs/instructions.md b/exercises/practice/allergies/.docs/instructions.md index a139492096..daf8cfde21 100644 --- a/exercises/practice/allergies/.docs/instructions.md +++ b/exercises/practice/allergies/.docs/instructions.md @@ -22,6 +22,6 @@ Now, given just that score of 34, your program should be able to say: - Whether Tom is allergic to any one of those allergens listed above. - All the allergens Tom is allergic to. -Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.). +Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.). Your program should ignore those components of the score. For example, if the allergy score is 257, your program should only report the eggs (1) allergy. diff --git a/exercises/practice/armstrong-numbers/.docs/instructions.md b/exercises/practice/armstrong-numbers/.docs/instructions.md index 744cfbe7fa..5e56bbe465 100644 --- a/exercises/practice/armstrong-numbers/.docs/instructions.md +++ b/exercises/practice/armstrong-numbers/.docs/instructions.md @@ -5,9 +5,9 @@ An [Armstrong number][armstrong-number] is a number that is the sum of its own d For example: - 9 is an Armstrong number, because `9 = 9^1 = 9` -- 10 is *not* an Armstrong number, because `10 != 1^2 + 0^2 = 1` +- 10 is _not_ an Armstrong number, because `10 != 1^2 + 0^2 = 1` - 153 is an Armstrong number, because: `153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153` -- 154 is *not* an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` +- 154 is _not_ an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` Write some code to determine whether a number is an Armstrong number. diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index aa1946cfb0..f183061e05 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -5,13 +5,13 @@ Your task is to implement a binary search algorithm. A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -~~~~exercism/caution +```exercism/caution Binary search only works when a list has been sorted. -~~~~ +``` The algorithm looks like this: -- Find the middle element of a *sorted* list and compare it with the item we're looking for. +- Find the middle element of a _sorted_ list and compare it with the item we're looking for. - If the middle element is our item, then we're done! - If the middle element is greater than our item, we can eliminate that element and all the elements **after** it. - If the middle element is less than our item, we can eliminate that element and all the elements **before** it. diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md index 906eb58761..54403f17bf 100644 --- a/exercises/practice/book-store/.docs/instructions.md +++ b/exercises/practice/book-store/.docs/instructions.md @@ -36,8 +36,8 @@ This would give a total of: Resulting in: -- 5 × (100% - 25%) * $8 = 5 × $6.00 = $30.00, plus -- 3 × (100% - 10%) * $8 = 3 × $7.20 = $21.60 +- 5 × (100% - 25%) × $8 = 5 × $6.00 = $30.00, plus +- 3 × (100% - 10%) × $8 = 3 × $7.20 = $21.60 Which equals $51.60. @@ -53,8 +53,8 @@ This would give a total of: Resulting in: -- 4 × (100% - 20%) * $8 = 4 × $6.40 = $25.60, plus -- 4 × (100% - 20%) * $8 = 4 × $6.40 = $25.60 +- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60, plus +- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60 Which equals $51.20. diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md index ddce7ee489..60ccad1b61 100644 --- a/exercises/practice/bowling/.docs/instructions.md +++ b/exercises/practice/bowling/.docs/instructions.md @@ -23,9 +23,9 @@ There are three cases for the tabulation of a frame. Here is a three frame example: -| Frame 1 | Frame 2 | Frame 3 | -| :-------------: |:-------------:| :---------------------:| -| X (strike) | 5/ (spare) | 9 0 (open frame) | +| Frame 1 | Frame 2 | Frame 3 | +| :--------: | :--------: | :--------------: | +| X (strike) | 5/ (spare) | 9 0 (open frame) | Frame 1 is (10 + 5 + 5) = 20 diff --git a/exercises/practice/circular-buffer/.docs/instructions.md b/exercises/practice/circular-buffer/.docs/instructions.md index 3487a0f614..2ba1fda2aa 100644 --- a/exercises/practice/circular-buffer/.docs/instructions.md +++ b/exercises/practice/circular-buffer/.docs/instructions.md @@ -4,39 +4,55 @@ A circular buffer, cyclic buffer or ring buffer is a data structure that uses a A circular buffer first starts empty and of some predefined length. For example, this is a 7-element buffer: - - [ ][ ][ ][ ][ ][ ][ ] + +```text +[ ][ ][ ][ ][ ][ ][ ] +``` Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer): - - [ ][ ][ ][1][ ][ ][ ] + +```text +[ ][ ][ ][1][ ][ ][ ] +``` Then assume that two more elements are added — 2 & 3 — which get appended after the 1: - - [ ][ ][ ][1][2][3][ ] + +```text +[ ][ ][ ][1][2][3][ ] +``` If two elements are then removed from the buffer, the oldest values inside the buffer are removed. The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3: - - [ ][ ][ ][ ][ ][3][ ] + +```text +[ ][ ][ ][ ][ ][3][ ] +``` If the buffer has 7 elements then it is completely full: - - [5][6][7][8][9][3][4] + +```text +[5][6][7][8][9][3][4] +``` When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free. When the buffer is full, the client can opt to overwrite the oldest data with a forced write. In this case, two more elements — A & B — are added and they overwrite the 3 & 4: - - [5][6][7][8][9][A][B] + +```text +[5][6][7][8][9][A][B] +``` 3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer. Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer: - - [ ][ ][7][8][9][A][B] + +```text +[ ][ ][7][8][9][A][B] +``` Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8. 7 is still the oldest element and the buffer is once again full. - - [C][D][7][8][9][A][B] + +```text +[C][D][7][8][9][A][B] +``` diff --git a/exercises/practice/dot-dsl/.docs/instructions.md b/exercises/practice/dot-dsl/.docs/instructions.md index 9230547ea5..b3a63996d8 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.md +++ b/exercises/practice/dot-dsl/.docs/instructions.md @@ -1,7 +1,7 @@ # Instructions A [Domain Specific Language (DSL)][dsl] is a small language optimized for a specific domain. -Since a DSL is targeted, it can greatly impact productivity/understanding by allowing the writer to declare *what* they want rather than *how*. +Since a DSL is targeted, it can greatly impact productivity/understanding by allowing the writer to declare _what_ they want rather than _how_. One problem area where they are applied are complex customizations/configurations. diff --git a/exercises/practice/etl/.docs/instructions.md b/exercises/practice/etl/.docs/instructions.md index 802863b540..7bb161f8b7 100644 --- a/exercises/practice/etl/.docs/instructions.md +++ b/exercises/practice/etl/.docs/instructions.md @@ -22,6 +22,6 @@ This needs to be changed to store each individual letter with its score in a one As part of this change, the team has also decided to change the letters to be lower-case rather than upper-case. -~~~~exercism/note +```exercism/note If you want to look at how the data was previously structured and how it needs to change, take a look at the examples in the test suite. -~~~~ +``` diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md index 18a3dc2005..74afaa994f 100644 --- a/exercises/practice/gigasecond/.docs/introduction.md +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -13,7 +13,7 @@ Then we can use metric system prefixes for writing large numbers of seconds in m - Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). - And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. -~~~~exercism/note +```exercism/note If we ever colonize Mars or some other planet, measuring time is going to get even messier. If someone says "year" do they mean a year on Earth or a year on Mars? @@ -21,4 +21,4 @@ The idea for this exercise came from the science fiction novel ["A Deepness in t In it the author uses the metric system as the basis for time measurements. [vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ -~~~~ +``` diff --git a/exercises/practice/isogram/.docs/instructions.md b/exercises/practice/isogram/.docs/instructions.md index 5e48844762..2e8df851a9 100644 --- a/exercises/practice/isogram/.docs/instructions.md +++ b/exercises/practice/isogram/.docs/instructions.md @@ -11,4 +11,4 @@ Examples of isograms: - downstream - six-year-old -The word *isograms*, however, is not an isogram, because the s repeats. +The word _isograms_, however, is not an isogram, because the s repeats. diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index 1dbbca91c2..fadcee1b18 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -13,10 +13,12 @@ Given a knapsack with a specific carrying capacity (W), help Bob determine the m Note that Bob can take only one of each item. All values given will be strictly positive. -Items will be represented as a list of pairs, `wi` and `vi`, where the first element `wi` is the weight of the *i*th item and `vi` is the value for that item. +Items will be represented as a list of items. +Each item will have a weight and value. For example: +```none Items: [ { "weight": 5, "value": 10 }, { "weight": 4, "value": 40 }, @@ -25,6 +27,7 @@ Items: [ ] Knapsack Limit: 10 +``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. diff --git a/exercises/practice/linked-list/.docs/instructions.md b/exercises/practice/linked-list/.docs/instructions.md index edf4055b38..a47942d73d 100644 --- a/exercises/practice/linked-list/.docs/instructions.md +++ b/exercises/practice/linked-list/.docs/instructions.md @@ -13,7 +13,7 @@ Sometimes a station gets closed down, and in that case the station needs to be r The size of a route is measured not by how far the train travels, but by how many stations it stops at. -~~~~exercism/note +```exercism/note The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. As the name suggests, it is a list of nodes that are linked together. It is a list of "nodes", where each node links to its neighbor or neighbors. @@ -23,4 +23,4 @@ In a **doubly linked list** each node links to both the node that comes before, If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. [intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d -~~~~ +``` diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md index ccfc2f8b2a..ebc5dffed0 100644 --- a/exercises/practice/list-ops/.docs/instructions.md +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -7,13 +7,13 @@ Implement a series of basic list operations, without using existing functions. The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include: -- `append` (*given two lists, add all items in the second list to the end of the first list*); -- `concatenate` (*given a series of lists, combine all items in all lists into one flattened list*); -- `filter` (*given a predicate and a list, return the list of all items for which `predicate(item)` is True*); -- `length` (*given a list, return the total number of items within it*); -- `map` (*given a function and a list, return the list of the results of applying `function(item)` on all items*); -- `foldl` (*given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left*); -- `foldr` (*given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right*); -- `reverse` (*given a list, return a list with all the original items, but in reversed order*). +- `append` (_given two lists, add all items in the second list to the end of the first list_); +- `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_); +- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_); +- `length` (_given a list, return the total number of items within it_); +- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_); +- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_); +- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_); +- `reverse` (_given a list, return a list with all the original items, but in reversed order_). Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md index 32b6f1fc31..d38fa341df 100644 --- a/exercises/practice/pangram/.docs/introduction.md +++ b/exercises/practice/pangram/.docs/introduction.md @@ -7,10 +7,10 @@ To give a comprehensive sense of the font, the random sentences should use **all They're running a competition to get suggestions for sentences that they can use. You're in charge of checking the submissions to see if they are valid. -~~~~exercism/note +```exercism/note Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". The best known English pangram is: > The quick brown fox jumps over the lazy dog. -~~~~ +``` diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 6d3275cdf2..6b86bbac9f 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -5,8 +5,8 @@ Clean up user-entered phone numbers so that they can be sent SMS messages. The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. -NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as *area code*, followed by a seven-digit local number. -The first three digits of the local number represent the *exchange code*, followed by the unique four-digit number which is the *subscriber number*. +NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as _area code_, followed by a seven-digit local number. +The first three digits of the local number represent the _exchange code_, followed by the unique four-digit number which is the _subscriber number_. The format is usually represented as diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index d9b9054cf5..7dc34d2edf 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -29,16 +29,16 @@ Note the stop codon `"UAA"` terminates the translation and the final methionine Below are the codons and resulting Amino Acids needed for the exercise. -Codon | Protein -:--- | :--- -AUG | Methionine -UUU, UUC | Phenylalanine -UUA, UUG | Leucine -UCU, UCC, UCA, UCG | Serine -UAU, UAC | Tyrosine -UGU, UGC | Cysteine -UGG | Tryptophan -UAA, UAG, UGA | STOP +| Codon | Protein | +| :----------------- | :------------ | +| AUG | Methionine | +| UUU, UUC | Phenylalanine | +| UUA, UUG | Leucine | +| UCU, UCC, UCA, UCG | Serine | +| UAU, UAC | Tyrosine | +| UGU, UGC | Cysteine | +| UGG | Tryptophan | +| UAA, UAG, UGA | STOP | Learn more about [protein translation on Wikipedia][protein-translation]. diff --git a/exercises/practice/rational-numbers/.docs/instructions.md b/exercises/practice/rational-numbers/.docs/instructions.md index f64fc0f28e..5de9966aed 100644 --- a/exercises/practice/rational-numbers/.docs/instructions.md +++ b/exercises/practice/rational-numbers/.docs/instructions.md @@ -2,11 +2,11 @@ A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`. -~~~~exercism/note +```exercism/note Note that mathematically, the denominator can't be zero. However in many implementations of rational numbers, you will find that the denominator is allowed to be zero with behaviour similar to positive or negative infinity in floating point numbers. In those cases, the denominator and numerator generally still can't both be zero at once. -~~~~ +``` The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`. diff --git a/exercises/practice/rest-api/.docs/instructions.md b/exercises/practice/rest-api/.docs/instructions.md index f3b226a73d..cb57f6f43f 100644 --- a/exercises/practice/rest-api/.docs/instructions.md +++ b/exercises/practice/rest-api/.docs/instructions.md @@ -20,7 +20,7 @@ Your task is to implement a simple [RESTful API][restful-wikipedia] that receive }, "owed_by": { "Bob": 6.5, - "Dan": 2.75, + "Dan": 2.75 }, "balance": "<(total owed by other users) - (total owed to other users)>" } @@ -28,11 +28,11 @@ Your task is to implement a simple [RESTful API][restful-wikipedia] that receive ### Methods -| Description | HTTP Method | URL | Payload Format | Response w/o Payload | Response w/ Payload | -| --- | --- | --- | --- | --- | --- | -| List of user information | GET | /users | `{"users":["Adam","Bob"]}` | `{"users":}` | `{"users": (sorted by name)}` | -| Create user | POST | /add | `{"user":}` | N/A | `` | -| Create IOU | POST | /iou | `{"lender":,"borrower":,"amount":5.25}` | N/A | `{"users": and (sorted by name)>}` | +| Description | HTTP Method | URL | Payload Format | Response w/o Payload | Response w/ Payload | +| ------------------------ | ----------- | ------ | ------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------- | +| List of user information | GET | /users | `{"users":["Adam","Bob"]}` | `{"users":}` | `{"users": (sorted by name)}` | +| Create user | POST | /add | `{"user":}` | N/A | `` | +| Create IOU | POST | /iou | `{"lender":,"borrower":,"amount":5.25}` | N/A | `{"users": and (sorted by name)>}` | ## Other Resources diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index 36da381f5a..f787be60bc 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -15,6 +15,6 @@ Given a DNA strand, its transcribed RNA strand is formed by replacing each nucle - `T` -> `A` - `A` -> `U` -~~~~exercism/note +```exercism/note If you want to look at how the inputs and outputs are structured, take a look at the examples in the test suite. -~~~~ +``` diff --git a/exercises/practice/rna-transcription/.docs/introduction.md b/exercises/practice/rna-transcription/.docs/introduction.md index 6b3f44b532..d74a8e84d2 100644 --- a/exercises/practice/rna-transcription/.docs/introduction.md +++ b/exercises/practice/rna-transcription/.docs/introduction.md @@ -4,7 +4,7 @@ You work for a bioengineering company that specializes in developing therapeutic Your team has just been given a new project to develop a targeted therapy for a rare type of cancer. -~~~~exercism/note +```exercism/note It's all very complicated, but the basic idea is that sometimes people's bodies produce too much of a given protein. That can cause all sorts of havoc. @@ -13,4 +13,4 @@ But if you can create a very specific molecule (called a micro-RNA), it can prev This technique is called [RNA Interference][rnai]. [rnai]: https://admin.acceleratingscience.com/ask-a-scientist/what-is-rnai/ -~~~~ +``` diff --git a/exercises/practice/rotational-cipher/.docs/instructions.md b/exercises/practice/rotational-cipher/.docs/instructions.md index 4dee51b355..4bf64ca1d3 100644 --- a/exercises/practice/rotational-cipher/.docs/instructions.md +++ b/exercises/practice/rotational-cipher/.docs/instructions.md @@ -22,8 +22,8 @@ Ciphertext is written out in the same formatting as the input including spaces a ## Examples -- ROT5 `omg` gives `trl` -- ROT0 `c` gives `c` +- ROT5 `omg` gives `trl` +- ROT0 `c` gives `c` - ROT26 `Cool` gives `Cool` - ROT13 `The quick brown fox jumps over the lazy dog.` gives `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` - ROT13 `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` gives `The quick brown fox jumps over the lazy dog.` diff --git a/exercises/practice/scale-generator/.docs/instructions.md b/exercises/practice/scale-generator/.docs/instructions.md index 23e06e1ab2..ebb7debc76 100644 --- a/exercises/practice/scale-generator/.docs/instructions.md +++ b/exercises/practice/scale-generator/.docs/instructions.md @@ -56,13 +56,13 @@ Then, for each interval in the pattern, the next note is determined by starting For example, starting with G and using the seven intervals MMmMMMm, there would be the following eight notes: -Note | Reason ---|-- -G | Tonic -A | M indicates a whole step from G, skipping G♯ -B | M indicates a whole step from A, skipping A♯ -C | m indicates a half step from B, skipping nothing -D | M indicates a whole step from C, skipping C♯ -E | M indicates a whole step from D, skipping D♯ -F♯ | M indicates a whole step from E, skipping F -G | m indicates a half step from F♯, skipping nothing +| Note | Reason | +| ---- | ------------------------------------------------- | +| G | Tonic | +| A | M indicates a whole step from G, skipping G♯ | +| B | M indicates a whole step from A, skipping A♯ | +| C | m indicates a half step from B, skipping nothing | +| D | M indicates a whole step from C, skipping C♯ | +| E | M indicates a whole step from D, skipping D♯ | +| F♯ | M indicates a whole step from E, skipping F | +| G | m indicates a half step from F♯, skipping nothing | diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index d2120b9bf2..b825c12895 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -41,8 +41,8 @@ The secret handshake for 26 is therefore: jump, double blink ``` -~~~~exercism/note +```exercism/note If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. [intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa -~~~~ +``` diff --git a/exercises/practice/series/.docs/instructions.md b/exercises/practice/series/.docs/instructions.md index e32cc38c67..fd97a6706a 100644 --- a/exercises/practice/series/.docs/instructions.md +++ b/exercises/practice/series/.docs/instructions.md @@ -15,5 +15,5 @@ And the following 4-digit series: And if you ask for a 6-digit series from a 5-digit string, you deserve whatever you get. -Note that these series are only required to occupy *adjacent positions* in the input; -the digits need not be *numerically consecutive*. +Note that these series are only required to occupy _adjacent positions_ in the input; +the digits need not be _numerically consecutive_. diff --git a/exercises/practice/sgf-parsing/.docs/instructions.md b/exercises/practice/sgf-parsing/.docs/instructions.md index d38b341fc0..edc8d6b188 100644 --- a/exercises/practice/sgf-parsing/.docs/instructions.md +++ b/exercises/practice/sgf-parsing/.docs/instructions.md @@ -24,7 +24,7 @@ This is a tree with three nodes: "SZ", value = "19"). (FF indicates the version of SGF, C is a comment and SZ is the size of the board.) - The top level node has a single child which has a single property: - B\[aa\]. (Black plays on the point encoded as "aa", which is the + B\[aa\]. (Black plays on the point encoded as "aa", which is the 1-1 point). - The B\[aa\] node has a single child which has a single property: W\[ab\]. diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 3adf1d551b..ec14620ce4 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -18,11 +18,11 @@ Then you repeat the following steps: You keep repeating these steps until you've gone through every number in your list. At the end, all the unmarked numbers are prime. -~~~~exercism/note +```exercism/note [Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. A good first test is to check that you do not use division or remainder operations. [eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes -~~~~ +``` diff --git a/exercises/practice/simple-linked-list/.docs/instructions.md b/exercises/practice/simple-linked-list/.docs/instructions.md index 04640b1fb0..c3ff4cf311 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.md @@ -7,7 +7,7 @@ Given a range of numbers (the song IDs), create a singly linked list. Given a singly linked list, you should be able to reverse the list to play the songs in the opposite order. -~~~~exercism/note +```exercism/note The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. The simplest kind of linked list is a **singly** linked list. @@ -16,4 +16,4 @@ That means that each element (or "node") contains data, along with something tha If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. [intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d -~~~~ +``` diff --git a/exercises/practice/wordy/.docs/instructions.md b/exercises/practice/wordy/.docs/instructions.md index 0b9e67b6ca..aafb9ee54b 100644 --- a/exercises/practice/wordy/.docs/instructions.md +++ b/exercises/practice/wordy/.docs/instructions.md @@ -48,7 +48,7 @@ Since these are verbal word problems, evaluate the expression from left-to-right > What is 3 plus 2 multiplied by 3? -15 (i.e. not 9) +15 (i.e. not 9) ## Iteration 4 — Errors diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md index 163ba3792c..54fdb452f5 100644 --- a/exercises/practice/yacht/.docs/instructions.md +++ b/exercises/practice/yacht/.docs/instructions.md @@ -6,24 +6,24 @@ The score of a throw of the dice depends on category chosen. ## 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 | +| 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. +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. ## Task From e1c66c9d37502f2003cf0c8c31a934b871196a06 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 22 Sep 2023 01:29:19 -0700 Subject: [PATCH 524/826] Synced practice exercise metadata. (#3508) --- exercises/practice/binary-search-tree/.meta/config.json | 3 +-- exercises/practice/clock/.meta/config.json | 3 +-- exercises/practice/meetup/.meta/config.json | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/exercises/practice/binary-search-tree/.meta/config.json b/exercises/practice/binary-search-tree/.meta/config.json index 8750a7497f..34a93b9145 100644 --- a/exercises/practice/binary-search-tree/.meta/config.json +++ b/exercises/practice/binary-search-tree/.meta/config.json @@ -22,6 +22,5 @@ ] }, "blurb": "Insert and search for numbers in a binary tree.", - "source": "Josh Cheek", - "source_url": "https://twitter.com/josh_cheek" + "source": "Josh Cheek" } diff --git a/exercises/practice/clock/.meta/config.json b/exercises/practice/clock/.meta/config.json index 0f1b8fa94b..b56a4bd664 100644 --- a/exercises/practice/clock/.meta/config.json +++ b/exercises/practice/clock/.meta/config.json @@ -30,6 +30,5 @@ ] }, "blurb": "Implement a clock that handles times without dates.", - "source": "Pairing session with Erin Drummond", - "source_url": "https://twitter.com/ebdrummond" + "source": "Pairing session with Erin Drummond" } diff --git a/exercises/practice/meetup/.meta/config.json b/exercises/practice/meetup/.meta/config.json index a001dda143..9a08a5da71 100644 --- a/exercises/practice/meetup/.meta/config.json +++ b/exercises/practice/meetup/.meta/config.json @@ -30,5 +30,5 @@ }, "blurb": "Calculate the date of meetups.", "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month", - "source_url": "https://twitter.com/copiousfreetime" + "source_url": "http://www.copiousfreetime.org/" } From ca9bd6ffe7deec3f5ddb8738ae606a0e52c75cce Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 27 Sep 2023 23:36:49 +0200 Subject: [PATCH 525/826] Convert backtick (`) admonition fences to tildes (~) (#3509) * Convert backtick (`) admonition fences to tildes (~). * Updated Grains Test file to Fix CI [no important files changed] --------- Co-authored-by: BethanyG --- concepts/generators/about.md | 4 ++-- .../concept/plane-tickets/.docs/introduction.md | 4 ++-- .../practice/binary-search/.docs/instructions.md | 4 ++-- .../practice/bob/.approaches/answer-list/content.md | 8 ++++---- .../bob/.approaches/if-statements-nested/content.md | 8 ++++---- .../bob/.approaches/if-statements/content.md | 8 ++++---- exercises/practice/etl/.docs/instructions.md | 4 ++-- exercises/practice/gigasecond/.docs/introduction.md | 4 ++-- exercises/practice/grains/grains_test.py | 8 ++++---- .../isogram/.approaches/scrub-regex/content.md | 4 ++-- .../leap/.approaches/datetime-addition/content.md | 4 ++-- exercises/practice/linked-list/.docs/instructions.md | 4 ++-- .../practice/luhn/.approaches/recursion/content.md | 4 ++-- .../practice/pangram/.approaches/all/content.md | 4 ++-- .../practice/pangram/.approaches/bitfield/content.md | 4 ++-- exercises/practice/pangram/.docs/introduction.md | 4 ++-- .../pig-latin/.approaches/sets-and-slices/content.md | 4 ++-- .../practice/rational-numbers/.docs/instructions.md | 4 ++-- .../.approaches/translate-maketrans/content.md | 4 ++-- .../practice/rna-transcription/.docs/instructions.md | 4 ++-- .../practice/rna-transcription/.docs/introduction.md | 4 ++-- .../practice/secret-handshake/.docs/instructions.md | 4 ++-- exercises/practice/sieve/.docs/instructions.md | 4 ++-- .../simple-linked-list/.docs/instructions.md | 4 ++-- .../wordy/.approaches/dunder-getattribute/content.md | 12 ++++++------ exercises/practice/wordy/.approaches/introduction.md | 4 ++-- 26 files changed, 64 insertions(+), 64 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index 9a26ab5548..5995b27c0e 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -94,9 +94,9 @@ When `yield` is evaluated, it pauses the execution of the enclosing function and The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again. -```exercism/note +~~~~exercism/note Using `yield` expressions is prohibited outside of functions. -``` +~~~~ ```python >>> def infinite_sequence(): diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 5ab9d6d261..88917fc805 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -94,9 +94,9 @@ When `yield` is evaluated, it pauses the execution of the enclosing function and The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again. -```exercism/note +~~~~exercism/note Using `yield` expressions is prohibited outside of functions. -``` +~~~~ ```python >>> def infinite_sequence(): diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index f183061e05..12f4358ebc 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -5,9 +5,9 @@ Your task is to implement a binary search algorithm. A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -```exercism/caution +~~~~exercism/caution Binary search only works when a list has been sorted. -``` +~~~~ The algorithm looks like this: diff --git a/exercises/practice/bob/.approaches/answer-list/content.md b/exercises/practice/bob/.approaches/answer-list/content.md index b3894780e5..7bed62373f 100644 --- a/exercises/practice/bob/.approaches/answer-list/content.md +++ b/exercises/practice/bob/.approaches/answer-list/content.md @@ -22,12 +22,12 @@ Python doesn't _enforce_ having real constant values, but the `ANSWERS` list is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. It indicates that the value is not intended to be changed. -```exercism/note +~~~~exercism/note `ANSWERS` could prevent item reassignment by being defined as a [tuple](https://realpython.com/python-lists-tuples/#python-tuples) instead of a list. The items in a tuple cannot be changed, and the performance between a tuple and a list here is equivalent. The entire `ANSWERS` tuple could still be reassigned to another tuple, so uppercase letters would still be used to indicate that the `ANSWERS` tuple should not be changed. -``` +~~~~ The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input. If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing. @@ -37,11 +37,11 @@ A [ternary operator][ternary] is used for determining the score for a shout and The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase. -```exercism/note +~~~~exercism/note A cased character is one which differs between lowercase and uppercase. For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase. `a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase. -``` +~~~~ If `isupper` is `True`, then `is_shout` is given the value of `2`; otherwise, it is given the value of `0`. diff --git a/exercises/practice/bob/.approaches/if-statements-nested/content.md b/exercises/practice/bob/.approaches/if-statements-nested/content.md index 5867427afd..4d2b7f4f1e 100644 --- a/exercises/practice/bob/.approaches/if-statements-nested/content.md +++ b/exercises/practice/bob/.approaches/if-statements-nested/content.md @@ -20,11 +20,11 @@ def response(hey_bob): In this approach you have a series of `if` statements using the calculated variables to evaluate the conditions, some of which are nested. As soon as a `True` condition is found, the correct response is returned. -```exercism/note +~~~~exercism/note Note that there are no `elif` or `else` statements. If an `if` statement can return, then an `elif` or `else` is not needed. Execution will either return or will continue to the next statement anyway. -``` +~~~~ The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input. If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing. @@ -32,11 +32,11 @@ Since it doesn't matter if there is leading whitespace, the `rstrip` function is The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase. -```exercism/note +~~~~exercism/note A cased character is one which differs between lowercase and uppercase. For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase. `a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase. -``` +~~~~ The [`endswith`][endswith] method is used to determine if the input ends with a question mark. diff --git a/exercises/practice/bob/.approaches/if-statements/content.md b/exercises/practice/bob/.approaches/if-statements/content.md index 7d4d84e368..442262238b 100644 --- a/exercises/practice/bob/.approaches/if-statements/content.md +++ b/exercises/practice/bob/.approaches/if-statements/content.md @@ -20,11 +20,11 @@ def response(hey_bob): In this approach you have a series of `if` statements using the calculated variables to evaluate the conditions. As soon as a `True` condition is found, the correct response is returned. -```exercism/note +~~~~exercism/note Note that there are no `elif` or `else` statements. If an `if` statement can return, then an `elif` or `else` is not needed. Execution will either return or will continue to the next statement anyway. -``` +~~~~ The [`rstrip`][rstrip] method is applied to the input to eliminate any whitespace at the end of the input. If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the response for saying nothing. @@ -32,11 +32,11 @@ Since it doesn't matter if there is leading whitespace, the `rstrip` function is The [`isupper`][isupper] method is used to test that there is at least one cased character and that all cased characters are uppercase. -```exercism/note +~~~~exercism/note A cased character is one which differs between lowercase and uppercase. For example, `?` and `3` are not cased characters, as they do not change between lowercase and uppercase. `a` and `z` are cased characters, since their lowercase form changes to `A` and ` Z` when uppercase. -``` +~~~~ The [`endswith`][endswith] method is used to determine if the input ends with a question mark. diff --git a/exercises/practice/etl/.docs/instructions.md b/exercises/practice/etl/.docs/instructions.md index 7bb161f8b7..802863b540 100644 --- a/exercises/practice/etl/.docs/instructions.md +++ b/exercises/practice/etl/.docs/instructions.md @@ -22,6 +22,6 @@ This needs to be changed to store each individual letter with its score in a one As part of this change, the team has also decided to change the letters to be lower-case rather than upper-case. -```exercism/note +~~~~exercism/note If you want to look at how the data was previously structured and how it needs to change, take a look at the examples in the test suite. -``` +~~~~ diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md index 74afaa994f..18a3dc2005 100644 --- a/exercises/practice/gigasecond/.docs/introduction.md +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -13,7 +13,7 @@ Then we can use metric system prefixes for writing large numbers of seconds in m - Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). - And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. -```exercism/note +~~~~exercism/note If we ever colonize Mars or some other planet, measuring time is going to get even messier. If someone says "year" do they mean a year on Earth or a year on Mars? @@ -21,4 +21,4 @@ The idea for this exercise came from the science fiction novel ["A Deepness in t In it the author uses the metric system as the basis for time measurements. [vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ -``` +~~~~ diff --git a/exercises/practice/grains/grains_test.py b/exercises/practice/grains/grains_test.py index 8d729fb6f8..177f91faa1 100644 --- a/exercises/practice/grains/grains_test.py +++ b/exercises/practice/grains/grains_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-09-27 import unittest @@ -32,19 +32,19 @@ def test_grains_on_square_32(self): def test_grains_on_square_64(self): self.assertEqual(square(64), 9223372036854775808) - def test_square_0_raises_an_exception(self): + def test_square_0_is_invalid(self): with self.assertRaises(ValueError) as err: square(0) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "square must be between 1 and 64") - def test_negative_square_raises_an_exception(self): + def test_negative_square_is_invalid(self): with self.assertRaises(ValueError) as err: square(-1) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "square must be between 1 and 64") - def test_square_greater_than_64_raises_an_exception(self): + def test_square_greater_than_64_is_invalid(self): with self.assertRaises(ValueError) as err: square(65) self.assertEqual(type(err.exception), ValueError) diff --git a/exercises/practice/isogram/.approaches/scrub-regex/content.md b/exercises/practice/isogram/.approaches/scrub-regex/content.md index 7c9c1b0dfe..1d66f43b36 100644 --- a/exercises/practice/isogram/.approaches/scrub-regex/content.md +++ b/exercises/practice/isogram/.approaches/scrub-regex/content.md @@ -12,9 +12,9 @@ def is_isogram(phrase): For this approach, [regular expression][regex], also known as a [regex][regex-how-to], is used to scrub the input phrase [str][str]ing. - In the pattern of `[^a-zA-Z]` the brackets are used to define a character set that looks for characters which are _not_ `a` through `z` and `A` through `Z`. -```exercism/note +~~~~exercism/note If the first character of a character set is `^`, all the characters that are _not_ in the rest of the character set will be matched. -``` +~~~~ This essentially matches any characters which are not in the English alphabet. The pattern is passed to the [`compile()`][compile] method to construct a [regular expression object][regex-object]. - The [`sub()`][sub] method is then called on the regex object. diff --git a/exercises/practice/leap/.approaches/datetime-addition/content.md b/exercises/practice/leap/.approaches/datetime-addition/content.md index 681e19101a..184869d131 100644 --- a/exercises/practice/leap/.approaches/datetime-addition/content.md +++ b/exercises/practice/leap/.approaches/datetime-addition/content.md @@ -10,9 +10,9 @@ def leap_year(year): ``` -```exercism/caution +~~~~exercism/caution This approach may be considered a "cheat" for this exercise. -``` +~~~~ By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. If it is the 29th, then the function returns `True` for the year being a leap year. diff --git a/exercises/practice/linked-list/.docs/instructions.md b/exercises/practice/linked-list/.docs/instructions.md index a47942d73d..edf4055b38 100644 --- a/exercises/practice/linked-list/.docs/instructions.md +++ b/exercises/practice/linked-list/.docs/instructions.md @@ -13,7 +13,7 @@ Sometimes a station gets closed down, and in that case the station needs to be r The size of a route is measured not by how far the train travels, but by how many stations it stops at. -```exercism/note +~~~~exercism/note The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. As the name suggests, it is a list of nodes that are linked together. It is a list of "nodes", where each node links to its neighbor or neighbors. @@ -23,4 +23,4 @@ In a **doubly linked list** each node links to both the node that comes before, If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. [intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d -``` +~~~~ diff --git a/exercises/practice/luhn/.approaches/recursion/content.md b/exercises/practice/luhn/.approaches/recursion/content.md index b6f5f67f7f..6bb89e7932 100644 --- a/exercises/practice/luhn/.approaches/recursion/content.md +++ b/exercises/practice/luhn/.approaches/recursion/content.md @@ -44,13 +44,13 @@ The `luhny_bin()` method takes that list, along with two `0` values that represe The `luhny_bin()` can call itself, which is a behavior called [recursion][recursion]. Since `luhny_bin()` can call itself, the first thing it does is to check that it is done calling itself. -```exercism/note +~~~~exercism/note This check is called the terminating condition. It's critical to have a terminating condition, since every call of a recursive function to itself places another [frame on the stack](https://realpython.com/lessons/stack-frames-and-stack-traces/#:~:text=A%20stack%20frame%20represents%20a,is%20removed%20from%20the%20stack.). If there is no terminating condition, then the recursive function will keep calling itself until the stack runs out of space and a stack overflow error will occur. -``` +~~~~ The `luhny_bin()` method should terminate when there are no more characters to process. By using the [falsiness][falsiness] of an empty list, the [`not` operator][not-operator] can be used instead of comparing the `len()` of the list to `0`. diff --git a/exercises/practice/pangram/.approaches/all/content.md b/exercises/practice/pangram/.approaches/all/content.md index ce09af36d5..478b011d2e 100644 --- a/exercises/practice/pangram/.approaches/all/content.md +++ b/exercises/practice/pangram/.approaches/all/content.md @@ -15,13 +15,13 @@ def is_pangram(sentence): using the [`all()`][all] function. - If all of the letters in the alphabet are contained in the `sentence`, then the function will return `True`. -```exercism/note +~~~~exercism/note Instead of `lower()`, the [`casefold`](https://docs.python.org/3/library/stdtypes.html#str.casefold) method could be used to lowercase the letters. `casefold()` differs from `lower()` in lowercasing certain Unicode characters. At the time of writing, those differences are not of concern to this exercise. Also, `casefold()` benched slower than `lower()`. -``` +~~~~ [ascii-lowercase]: https://docs.python.org/3/library/string.html#string.ascii_lowercase [lower]: https://docs.python.org/3/library/stdtypes.html?#str.lower diff --git a/exercises/practice/pangram/.approaches/bitfield/content.md b/exercises/practice/pangram/.approaches/bitfield/content.md index b292540567..69e0afec5b 100644 --- a/exercises/practice/pangram/.approaches/bitfield/content.md +++ b/exercises/practice/pangram/.approaches/bitfield/content.md @@ -20,11 +20,11 @@ def is_pangram(sentence): This solution uses the [ASCII][ascii] value of the letter to set the corresponding bit position. First, some [constant][const] values are set. -```exercism/note +~~~~exercism/note Python doesn't _enforce_ having real constant values, but using all uppercase letters is the naming convention for a Python constant. It indicates that the value is not intended to be changed. -``` +~~~~ These values will be used for readability in the body of the `is_pangram` function. The ASCII value for `a` is `97`. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md index d38fa341df..32b6f1fc31 100644 --- a/exercises/practice/pangram/.docs/introduction.md +++ b/exercises/practice/pangram/.docs/introduction.md @@ -7,10 +7,10 @@ To give a comprehensive sense of the font, the random sentences should use **all They're running a competition to get suggestions for sentences that they can use. You're in charge of checking the submissions to see if they are valid. -```exercism/note +~~~~exercism/note Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". The best known English pangram is: > The quick brown fox jumps over the lazy dog. -``` +~~~~ diff --git a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md index 54d5a6e809..13c47ed6c6 100644 --- a/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md +++ b/exercises/practice/pig-latin/.approaches/sets-and-slices/content.md @@ -42,11 +42,11 @@ If the beginning of the word matches either condition, the loop [continue][conti If the beginning of the word did not match either condition, that leaves [ranging][ranging] its characters from position 1 until the [`len()`][len] of the word. -```exercism/note +~~~~exercism/note When a [range](https://docs.python.org/3/library/stdtypes.html?#range) is provided two arguments, it generates values from the `start` argument up to _but not including_ the `stop` argument. This behavior can be referred to as start inclusive, stop exclusive. -``` +~~~~ The inner loop iterating characters is nested within the outer loop that iterates the words. Each character is iterated until finding a vowel (at this point, the letter `y` is now considered a vowel.) diff --git a/exercises/practice/rational-numbers/.docs/instructions.md b/exercises/practice/rational-numbers/.docs/instructions.md index 5de9966aed..f64fc0f28e 100644 --- a/exercises/practice/rational-numbers/.docs/instructions.md +++ b/exercises/practice/rational-numbers/.docs/instructions.md @@ -2,11 +2,11 @@ A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`. -```exercism/note +~~~~exercism/note Note that mathematically, the denominator can't be zero. However in many implementations of rational numbers, you will find that the denominator is allowed to be zero with behaviour similar to positive or negative infinity in floating point numbers. In those cases, the denominator and numerator generally still can't both be zero at once. -``` +~~~~ The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`. diff --git a/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md b/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md index fcd55730d9..9b484e3cb5 100644 --- a/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md +++ b/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md @@ -23,9 +23,9 @@ and is passed the translation table. The output of `translate()` is a string where all of the input DNA characters have been replaced by their RNA complement in the translation table. -```exercism/note +~~~~exercism/note As of this writing, no invalid DNA characters are in the argument to `to_rna()`, so there is no error handling required for invalid input. -``` +~~~~ [dictionaries]: https://docs.python.org/3/tutorial/datastructures.html?#dictionaries [maketrans]: https://docs.python.org/3/library/stdtypes.html?#str.maketrans diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index f787be60bc..36da381f5a 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -15,6 +15,6 @@ Given a DNA strand, its transcribed RNA strand is formed by replacing each nucle - `T` -> `A` - `A` -> `U` -```exercism/note +~~~~exercism/note If you want to look at how the inputs and outputs are structured, take a look at the examples in the test suite. -``` +~~~~ diff --git a/exercises/practice/rna-transcription/.docs/introduction.md b/exercises/practice/rna-transcription/.docs/introduction.md index d74a8e84d2..6b3f44b532 100644 --- a/exercises/practice/rna-transcription/.docs/introduction.md +++ b/exercises/practice/rna-transcription/.docs/introduction.md @@ -4,7 +4,7 @@ You work for a bioengineering company that specializes in developing therapeutic Your team has just been given a new project to develop a targeted therapy for a rare type of cancer. -```exercism/note +~~~~exercism/note It's all very complicated, but the basic idea is that sometimes people's bodies produce too much of a given protein. That can cause all sorts of havoc. @@ -13,4 +13,4 @@ But if you can create a very specific molecule (called a micro-RNA), it can prev This technique is called [RNA Interference][rnai]. [rnai]: https://admin.acceleratingscience.com/ask-a-scientist/what-is-rnai/ -``` +~~~~ diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index b825c12895..d2120b9bf2 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -41,8 +41,8 @@ The secret handshake for 26 is therefore: jump, double blink ``` -```exercism/note +~~~~exercism/note If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. [intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa -``` +~~~~ diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index ec14620ce4..3adf1d551b 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -18,11 +18,11 @@ Then you repeat the following steps: You keep repeating these steps until you've gone through every number in your list. At the end, all the unmarked numbers are prime. -```exercism/note +~~~~exercism/note [Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. A good first test is to check that you do not use division or remainder operations. [eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes -``` +~~~~ diff --git a/exercises/practice/simple-linked-list/.docs/instructions.md b/exercises/practice/simple-linked-list/.docs/instructions.md index c3ff4cf311..04640b1fb0 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.md @@ -7,7 +7,7 @@ Given a range of numbers (the song IDs), create a singly linked list. Given a singly linked list, you should be able to reverse the list to play the songs in the opposite order. -```exercism/note +~~~~exercism/note The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. The simplest kind of linked list is a **singly** linked list. @@ -16,4 +16,4 @@ That means that each element (or "node") contains data, along with something tha If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. [intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d -``` +~~~~ diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index dd3e6de39d..76280615bc 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -35,17 +35,17 @@ def answer(question): This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [dunder][dunder] methods. -```exercism/note +~~~~exercism/note They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. They are also called magic methods. -``` +~~~~ Since only whole numbers are involved, the dunder methods are those for [`int`][int]. The supported methods for `int` can be found by using `print(dir(int))`. -```exercism/note +~~~~exercism/note The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of valid attributes for an object. -``` +~~~~ Python doesn't _enforce_ having real constant values, but the `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. @@ -74,10 +74,10 @@ passing it `y` converted to an `int`. It sets the list to the result of the dunder method plus the remaining elements in `*tail`. -```exercism/note +~~~~exercism/note The `*` prefix in `*tail` [unpacks](https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/) the `tail` list back into its elements. This concept is also a part of [unpacking-and-multiple-assignment](https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment) concept in the syllabus. -``` +~~~~ When the loop exhausts, the first element of the list is selected as the function return value. diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index c8a5feb6f3..9faba81cbc 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -4,10 +4,10 @@ There are various ways to solve Wordy. Using [`eval`][eval] is a [convenient but potentially dangerous][eval-danger] approach. Another approach could replace the operation words with [dunder][dunder] methods. -```exercism/note +~~~~exercism/note They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. They are also called magic methods. -``` +~~~~ The dunder methods can be called by using the [`__getattribute__`][getattribute] method for [`int`][int]. From 16f9e717b9e9c8eae359ef54960d64b0a5e5c606 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 7 Oct 2023 15:32:06 -0700 Subject: [PATCH 526/826] [Sets/Cater-Waiter]: Rework Concept Docs (#3456) * Reworked and shortened concept and introduction docs. * Added Exercism note about symmetric difference to `about.md`. * Final intro touchups. * Further touchups to terminal examples. Removed iPython refs. * Update concepts/sets/about.md * Slight wording change to one test [no important files changed] --- concepts/sets/about.md | 477 ++++++++++++------ concepts/sets/introduction.md | 28 +- exercises/concept/cater-waiter/.docs/hints.md | 4 +- .../cater-waiter/.docs/introduction.md | 456 ++++++++++------- exercises/concept/cater-waiter/sets_test.py | 4 +- 5 files changed, 628 insertions(+), 341 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 5f272402ee..0b8fc842f1 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -1,119 +1,151 @@ # Sets -A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects. -Items within a `set` are distinct and duplicate members are not allowed. -Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable]. -Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor. +A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. +Set members must be distinct -- duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Sets also come in an immutable [`frozensets`][type-frozenset] flavor. -Like other collection types, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, and iteration via `for item in `. -_Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors. +Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. +They are also used for efficient comparisons when sequencing and duplicate tracking are not needed. -`sets` are most commonly used to quickly dedupe groups of items. -They're also used for fast membership testing, finding supersets & subsets of items, and performing "set math" (_calculating union, intersection, difference & symmetric difference between groups of items._). +Like other collection types (_dictionaries, lists, tuples_), `sets` support: +- Iteration via `for item in ` +- Membership checking via `in` and `not in`, +- Length calculation through `len()`, and +- Shallow copies through `copy()` -Checking membership in a `set` has only O(1) time complexity versus checking for membership in a `list` or `string`, which has worst-case O(n) time complexity. -Operations such as `.union()`, `.intersection()`, or `.difference()` have an average O(n) time complexity. +`sets` do not support: +- Indexing of any kind +- Ordering via sorting or insertion +- Slicing +- Concatenation via `+` -## Construction -A `set` can be declared as a _set literal_ with curly `{}` brackets and commas between elements. +Checking membership in a `set` has constant time complexity (on average) versus checking membership in a `list` or `string`, where the time complexity grows as the length of the data increases. +Methods such as `.union()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). + + +## Set Construction + +While sets can be created in many different ways, the most straightforward construction methods are declaring a _set literal_, using the `set` class constructor (`set()`), and using a _set comprehension_. + +### Set Literals + +A `set` can be directly entered as a _set literal_ with curly `{}` brackets and commas between elements. Duplicates are silently omitted: ```python >>> one_element = {'😀'} ->>> one_element {'😀'} ->>> multiple_elements = {'Hello!', '¡Hola!', 'Привет!', 'こんにちは!'} ->>> multiple_elements -{'こんにちは!', '¡Hola!', 'Hello!', 'Привет!'} +>>> multiple_elements = {'😀', '😃', '😄', '😁'} +{'😀', '😃', '😄', '😁'} ->>> multiple_duplicates = {'Hello!', '¡Hola!', 'Привет!', 'こんにちは!', '¡Hola!', 'Привет!'} ->>> multiple_duplicates -{'こんにちは!', '¡Hola!', 'Hello!', 'Привет!'} +>>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!', + '¡Hola!','Привіт!', 'こんにちは!', + '¡Hola!','Привіт!', 'こんにちは!'} +{'こんにちは!', '¡Hola!', 'Hello!', 'Привіт!'} ``` -Set literals use the same curly braces as `dict` literals, so the `set()` constructor must be used to declare an empty `set`. +Set literals use the same curly braces as `dict` literals, which means you need to use `set()` to create an empty `set`. + +### The Set Constructor + +`set()` (_the constructor for the `set` class_) can be used with any `iterable` passed as an argument. +Elements of the `iterable` are cycled through and added to the `set` individually. +Element order is not preserved and duplicates are silently omitted: -The `set()` constructor can also be used with any _iterable_ passed as an argument. -Elements are cycled through by the constructor and added to the `set` individually. -Order is not preserved and duplicates are silently omitted: ```python +# To create an empty set, the constructor must be used. >>> no_elements = set() ->>> no_elements set() -# The tuple is unpacked and each distinct element is added. Duplicates are removed. ->>> multiple_elements_from_tuple = set(("Parrot", "Bird", 334782, "Bird", "Parrot")) ->>> multiple_elements_from_tuple +# The tuple is unpacked & each element is added. +# Duplicates are removed. +>>> elements_from_tuple = set(("Parrot", "Bird", + 334782, "Bird", "Parrot")) {334782, 'Bird', 'Parrot'} -# The list is unpacked and each distinct element is added. ->>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13]) ->>> multiple_elements_from_set +# The list is unpacked & each element is added. +# Duplicates are removed. +>>> elements_from_list = set([2, 3, 2, 3, 3, 3, 5, + 7, 11, 7, 11, 13, 13]) {2, 3, 5, 7, 11, 13} ``` -Results when using a set constructor with a string or dictionary may be surprising: +### Set Comprehensions + +Like `lists` and `dicts`, sets can be created via _comprehension_: + +```python +# First, a list with duplicates +>>> numbers = [1,2,3,4,5,6,6,5,4,8,9,9,9,2,3,12,18] + +# This set comprehension squares the numbers divisible by 3 +# Duplicates are removed. +>>> calculated = {item**2 for item in numbers if item % 3 == 0} +{9, 36, 81, 144, 324} +``` + +### Gotchas when Creating Sets + +Due to its "unpacking" behavior, using the `set` constructor with a string might be surprising: ```python -# String elements (Unicode code points) are iterated through and added *individually*. ->>> multiple_elements_string = set("Timbuktu") ->>> multiple_elements_string +# String elements (Unicode code points) are +# iterated through and added *individually*. +>>> elements_string = set("Timbuktu") {'T', 'b', 'i', 'k', 'm', 't', 'u'} -# Unicode separators and positioning code points are also added *individually*. +# Unicode separators and positioning code points +# are also added *individually*. >>> multiple_code_points_string = set('अभ्यास') ->>> multiple_code_points_string {'अ', 'भ', 'य', 'स', 'ा', '्'} - -# The iteration default for dictionaries is over the keys. ->>> source_data = {"fish": "gold", "monkey": "brown", "duck" : "white", "crow": "black"} ->>> set(source_data) -{'crow', 'duck', 'fish', 'monkey'} ``` -Sets can hold heterogeneous datatypes, but all `set` elements must be _hashable_: +Remember: sets can hold different datatypes and _nested_ datatypes, but all `set` elements must be _hashable_: ```python - ->>> lists_as_elements = {['😅','🤣'], ['😂','🙂','🙃'], ['😜', '🤪', '😝']} +# Attempting to use a list for a set member throws a TypeError +>>> lists_as_elements = {['😅','🤣'], + ['😂','🙂','🙃'], + ['😜', '🤪', '😝']} Traceback (most recent call last): - - File "", line 1, in - lists_as_elements = {['😅','🤣'], ['😂','🙂','🙃'], ['😜', '🤪', '😝']} - + File "", line 1, in TypeError: unhashable type: 'list' -# standard sets are mutable, so they cannot be hashed. ->>> sets_as_elements = {{'😅','🤣'}, {'😂','🙂','🙃'}, {'😜', '🤪', '😝'}} -Traceback (most recent call last): - File "", line 1, in - sets_as_elements = {{'😅','🤣'}, {'😂','🙂','🙃'}, {'😜', '🤪', '😝'}} +# Standard sets are mutable, so they cannot be hashed. +>>> sets_as_elements = {{'😅','🤣'}, + {'😂','🙂','🙃'}, + {'😜', '🤪', '😝'}} +Traceback (most recent call last): + File "", line 1, in TypeError: unhashable type: 'set' ``` -Therefore, to create a `set` of `sets`, the contained sets must be of type `frozenset()` +However, a `set` of `sets` can be created via type `frozenset()`: ```python -# frozensets don't have a literal form +# Frozensets don't have a literal form. >>> set_1 = frozenset({'😜', '😝', '🤪'}) >>> set_2 = frozenset({'😅', '🤣'}) >>> set_3 = frozenset({'😂', '🙂', '🙃'}) >>> frozen_sets_as_elements = {set_1, set_2, set_3} >>> frozen_sets_as_elements -{frozenset({'😜', '😝', '🤪'}), frozenset({'😅', '🤣'}), frozenset({'😂', '🙂', '🙃'})} +{frozenset({'😜', '😝', '🤪'}), frozenset({'😅', '🤣'}), +frozenset({'😂', '🙂', '🙃'})} ``` -## Working with Sets -Elements can be added/removed using `.add()` / `.remove()`. -`remove()` will raise a `KeyError` if the item is not present in the `set`. +## Adding and Removing Set Members + +Elements can be added or removed from a `set` using the methods `.add()` and `.remove()`. +The `.remove()` method will raise a `KeyError` if the item is not present in the `set`: ```python >>> creatures = {'crow', 'duck', 'fish', 'monkey', 'elephant'} @@ -122,100 +154,139 @@ Elements can be added/removed using `.add()` / `.remove()` >>> creatures {'beaver', 'crow', 'elephant', 'fish', 'monkey'} -# Trying to remove an item that is not present will raise a KeyError +# Trying to remove an item that is not present raises a KeyError >>> creatures.remove('bear') Traceback (most recent call last): - - File "", line 1, in - creatures.remove('bear') - -KeyError: 'bear' + File "", line 1, in + KeyError: 'bear' ``` -`.discard()` will also remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present. -`.clear()` will remove all items. -`.pop()` will remove and _return_ an **arbitrary** item and raises a `KeyError` if the `set` is empty. +### Additional Strategies for Removing Set Members -## Set Methods +- `.discard()` will remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present. +- `.clear()` will remove all items from the set. +- `.pop()` will remove and _return_ an **arbitrary** item, and raises a `KeyError` if the `set` is empty. -Sets implement methods that generally mimic [mathematical set operations][mathematical-sets]. -Most (_though not all_) of these methods can be performed using either operator(s) or method call(s). -Using operators requires that both inputs be `sets` or `frozensets`, while methods will generally take any iterable as an argument. -### Fast Membership Testing Between Groups +## Set Operations -The `.isdisjoint()` method is used to test if a `set` has **no elements in common** with another set or iterable. -It will accept any `iterable` or `set` as an arugment, returning `True` if they are **disjoint**, `False` otherwise. -Note that for `dcts`, the iteration default is over`.keys()`. +Sets have methods that generally mimic [mathematical set operations][mathematical-sets]. +Most (_not all_) of these methods have an [operator][operator] equivalent. +Methods generally take any `iterable` as an argument, while operators require that both things being compared are `sets` or `frozensets`. -```python ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} -# Dictionary of animal names with colors ->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white', - 'albatross': 'grey and white','crow': 'black','elephant': 'grey', - 'dog': 'rust','cow': 'black and white','tiger': 'orange and black', - 'cat': 'grey','squirrel': 'black'} +### Membership Testing Between Sets -# List of additonal animals ->>> additional_animals = ['pangolin', 'panda', 'parrot', 'lemur', 'tiger', 'pangolin'] -... +The `.isdisjoint()` method is used to test if a `sets` elements have any overlap with the elements of another. +The method will accept any `iterable` or `set` as an argument. +It will return `True` if the two sets have **no elements in common**, `False` if elements are **shared**. ->>> mammals.isdisjoint(birds) +```python +# Both mammals and additional_animals are lists. +>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant'] +>>> additional_animals = ['pangolin', 'panda', 'parrot', + 'lemur', 'tiger', 'pangolin'] + +# Animals is a dict. +>>> animals = {'chicken': 'white', + 'sparrow': 'grey', + 'eagle': 'brown and white', + 'albatross': 'grey and white', + 'crow': 'black', + 'elephant': 'grey', + 'dog': 'rust', + 'cow': 'black and white', + 'tiger': 'orange and black', + 'cat': 'grey', + 'squirrel': 'black'} + +# Birds is a set. +>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} + +# Mammals and birds don't share any elements. +>>> birds.isdisjoint(mammals) True ->>> mammals.isdisjoint(animals) -False - +# There are also no shared elements between +# additional_animals and birds. >>> birds.isdisjoint(additional_animals) True ->>> set(additional_animals).isdisjoint(animals) +# Animals and mammals have shared elements. +# **Note** The first object needs to be a set or converted to a set +# since .isdisjoint() is a set method. +>>> set(animals).isdisjoint(mammals) False ``` -`.issubset()` | ` <= ` are used to check if every element in `` is also in ``. -`.issuperset()` | ` >= ` are used to check the inverse -- if every element in `` is also in ``. +### Checking for Subsets and Supersets -```python ->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white', - 'albatross': 'grey and white','crow': 'black','elephant': 'grey', - 'dog': 'rust','cow': 'black and white','tiger': 'organge and black', - 'cat': 'grey','squirrel': 'black'} +`.issubset()` is used to check if every element in `` is also in ``. +The operator form is ` <= `: ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} +```python +# Set methods will take any iterable as an argument. +# All members of birds are also members of animals. +>>> birds.issubset(animals) +True -# Methods will take any iterable as an argument ->>> mammals.issubset(animal_colors) +# All members of mammals also appear in animals. +# **Note** The first object needs to be a set or converted to a set +# since .issubset() is a set method. +>>> set(mammals).issubset(animals) True +# Both objects need to be sets to use a set operator +>>> birds <= set(mammals) +False -# A set is always a loose subset of itself ->>> animals <= animals +# A set is always a loose subset of itself. +>>> set(additional_animals) <= set(additional_animals) True +``` + +`.issuperset()` is the inverse of `.issubset()`. +It is used to check if every element in `` is also in ``. +The operator form is ` >= `: ->>> birds <= animals + +```python +# All members of mammals also appear in animals. +# **Note** The first object needs to be a set or converted to a set +# since .issuperset() is a set method. +>>> set(animals).issuperset(mammals) True ->>> birds <= mammals +# All members of animals do not show up as members of birds. +>>> birds.issuperset(animals) False + +# Both objects need to be sets to use a set operator +>>> birds >= set(mammals) +False + +# A set is always a loose superset of itself. +>>> set(animals) <= set(animals) +True ``` -` < ` and ` > ` are used to test for _proper subsets_: -(`` <= ``) AND (`` != ``) for the `<` operator; (`` >= ``) AND (`` != ``) for the `>` operator. -They have no method equivelent. +### 'Proper' Subsets and Supersets + +` < ` and ` > ` are used to test for _proper subsets_. +A `set` is a proper subset if (`` <= ``) **AND** (`` != ``) for the `<` operator. + +A `set is a proper superset if `(`` >= ``) **AND** (`` != ``) for the `>` operator. +These operators have no method equivalent: ```python ->>> animal_names = {'albatross','cat','chicken','cow','crow','dog', - 'eagle','elephant','sparrow','squirrel','tiger'} +>>> animal_names = {'albatross','cat','chicken','cow','crow','dog', + 'eagle','elephant','sparrow','squirrel','tiger'} ->>> animal_names_also = {'albatross','cat','chicken','cow','crow','dog', - 'eagle','elephant','sparrow','squirrel','tiger'} +>>> animals_also = {'albatross','cat','chicken','cow','crow','dog', + 'eagle','elephant','sparrow','squirrel','tiger'} ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} +>>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} +>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} >>> mammals < animal_names True @@ -223,81 +294,114 @@ True >>> animal_names > birds True -# A set is never a *proper subset* of itself ->>> animal_names_also < animal_names +# A set is not a *proper subset* if set == other set. +>>> animals_also < animal_names False - ->>> animals < animals - +# A set is never a *proper subset* of itself +>>> animals_also < animals_also +False ``` -### Set Operations +### Set Unions -`.union(*)` and ` | | | ... | ` return a new `set` with elements from `` and all ``. +`.union(*)` returns a new `set` with elements from `` and all ``. +The operator form of this method is ` | | | ... | `. ```python ->>> perennial_vegetables = {'Asparagus', 'Broccoli', 'Sweet Potatoe', 'Kale'} ->>> annual_vegetables = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'} - ->>> more_perennials = ['Radicchio', 'Rhubarb', 'Spinach', 'Watercress'] +>>> perennials = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'} +>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'} +>>> more_perennials = ['Radicchio', 'Rhubarb', + 'Spinach', 'Watercress'] # Methods will take any iterable as an argument. ->>> perennial_vegetables.union(more_perennials) -{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb','Spinach','Sweet Potatoe','Watercress'} +>>> perennials.union(more_perennials) +{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb', +'Spinach','Sweet Potato','Watercress'} # Operators require sets. ->>> perennial_vegetables | annual_vegetables -{'Asparagus','Broccoli','Corn','Kale','Summer Squash','Sweet Peas','Sweet Potatoe','Zucchini'} - +>>> set(more_perennials) | perennials +{'Asparagus', + 'Broccoli', + 'Kale', + 'Radicchio', + 'Rhubarb', + 'Spinach', + 'Sweet Potato', + 'Watercress'} ``` -`.difference(*)` and ` - - - ...` return a new `set` with elements from the original `` that are not in ``. +### Set Differences + +`.difference(*)` returns a new `set` with elements from the original `` that are not in ``. +The operator version of this method is ` - - - ...`. ```python ->>> berries_and_veggies = {'Asparagus', 'Broccoli', 'Watercress', 'Goji Berries', 'Goose Berries', 'Ramps', - 'Walking Onions', 'Raspberries','Blueberries', 'Blackberries', 'Strawberries', - 'Rhubarb', 'Kale', 'Artichokes', 'Currants', 'Honeyberries'} +>>> berries_and_veggies = {'Asparagus', + 'Broccoli', + 'Watercress', + 'Goji Berries', + 'Goose Berries', + 'Ramps', + 'Walking Onions', + 'Blackberries', + 'Strawberries', + 'Rhubarb', + 'Kale', + 'Artichokes', + 'Currants'} -# Methods will take any iterable as an argument. >>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps', 'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes') ->>> just_berries = berries_and_veggies.difference(veggies) ->>> just_berries -{'Blackberries','Blueberries','Currants','Goji Berries', - 'Goose Berries','Honeyberries','Raspberries','Strawberries'} +# Methods will take any iterable as an argument. +>>> berries = berries_and_veggies.difference(veggies) +{'Blackberries','Currants','Goji Berries', + 'Goose Berries', 'Strawberries'} +# Operators require sets. >>> berries_and_veggies - just_berries -{'Artichokes','Asparagus','Broccoli','Kale','Ramps','Rhubarb','Walking Onions','Watercress'} +{'Artichokes','Asparagus','Broccoli','Kale', +'Ramps','Rhubarb','Walking Onions','Watercress'} ``` -`.intersection(*)` and ` & & & ... ` return a new `set` with elements common to the original `set` and all ``. +### Set Intersections + +`.intersection(*)` returns a new `set` with elements common to the original `set` and all `` (in other words, the `set` where everything [intersects][intersection]). +The operator version of this method is ` & & & ... ` ```python ->>> perennials = {'Annatto','Asafetida','Asparagus','Azalea','Winter Savory', 'Blackberries','Broccoli','Curry Leaf', - 'Fennel','French Sorrel','Fuchsia','Kaffir Lime','Kale','Lavender','Mint','Oranges', - 'Oregano','Ramps','Roses','Tarragon','Watercress','Wild Bergamot'} +>>> perennials = {'Annatto','Asafetida','Asparagus','Azalea', + 'Winter Savory', 'Broccoli','Curry Leaf','Fennel', + 'Kaffir Lime','Kale','Lavender','Mint','Oranges', + 'Oregano', 'Tarragon', 'Wild Bergamot'} ->>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', 'Summer Squash', 'Okra', - 'Shallots', 'Basil', 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', 'Summer Savory'} +>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', + 'Summer Squash', 'Okra','Shallots', 'Basil', + 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', + 'Summer Savory'} ->>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro','Curry Leaf','Fennel','Kaffir Lime', - 'Lavender','Marjoram','Mint','Oregano','Summer Savory' 'Tarragon','Wild Bergamot', - 'Wild Celery','Winter Savory'] +>>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro', + 'Curry Leaf','Fennel','Kaffir Lime','Lavender', + 'Marjoram','Mint','Oregano','Summer Savory' + 'Tarragon','Wild Bergamot','Wild Celery', + 'Winter Savory'] # Methods will take any iterable as an argument. >>> perennial_herbs = perennials.intersection(herbs) ->>> perennial_herbs -{'Mint', 'Annatto', 'Winter Savory', 'Curry Leaf', 'Lavender', 'Fennel', - 'Oregano', 'Kaffir Lime','Asafetida', 'Wild Bergamot', 'Tarragon'} +{'Annatto', 'Asafetida', 'Curry Leaf', 'Fennel', 'Kaffir Lime', + 'Lavender', 'Mint', 'Oregano', 'Wild Bergamot','Winter Savory'} +# Operators require both groups be sets. >>> annuals & set(herbs) {'Basil', 'Chervil', 'Marjoram', 'Cilantro'} ``` -`.symmetric_difference()` and ` ^ ` return a new `set` that contains elements that are in `` OR ``, but **not in both**. +### Set Symmetric Differences + +`.symmetric_difference()` returns a new `set` that contains elements that are in `` OR ``, but **not in both**. +The operator version of this method is ` ^ `. ```python >>> plants_1 = {'🌲','🍈','🌵', '🥑','🌴', '🥭'} @@ -309,6 +413,8 @@ False >>> fruit_and_flowers {'🌸', '🌺', '🍈', '🥑', '🥭','🌻' } + +# Operators require both groups be sets. >>> fruit_and_flowers ^ plants_1 {'🌲', '🌸', '🌴', '🌵','🌺', '🌻'} @@ -316,7 +422,62 @@ False { '🥑', '🌴','🌲', '🌵', '🍈', '🥭'} ``` -[type-set]: https://docs.python.org/3/library/stdtypes.html#set -[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset -[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation +~~~~exercism/note + +A symmetric difference of more than two sets will result in a `set` that includes both the elements unique to each `set` AND elements shared between more than two sets in the series (_details in the Wikipedia article on [symmetric difference][symmetric_difference]_). + +To obtain only items unique to each `set` in the series, intersections between all 2-set combinations need to be aggregated in a separate step, and removed: + + +```python +>>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour', + 'flour','lemon','parsley','salt','soy sauce', + 'sunflower oil','water'} + +>>> two = {'black pepper','cornstarch','garlic','ginger', + 'lemon juice','lemon zest','salt','soy sauce','sugar', + 'tofu','vegetable oil','vegetable stock','water'} + +>>> three = {'black pepper','garlic','lemon juice','mixed herbs', + 'nutritional yeast', 'olive oil','salt','silken tofu', + 'smoked tofu','soy sauce','spaghetti','turmeric'} + +>>> four = {'barley malt','bell pepper','cashews','flour', + 'fresh basil','garlic','garlic powder', 'honey', + 'mushrooms','nutritional yeast','olive oil','oregano', + 'red onion', 'red pepper flakes','rosemary','salt', + 'sugar','tomatoes','water','yeast'} + +>>> intersections = (one & two | one & three | one & four | + two & three | two & four | three & four) +... +{'black pepper','flour','garlic','lemon juice','nutritional yeast', +'olive oil','salt','soy sauce', 'sugar','water'} + +# The ^ operation will include some of the items in intersections, +# which means it is not a "clean" symmetric difference - there +# are overlapping members. +>>> (one ^ two ^ three ^ four) & intersections +{'black pepper', 'garlic', 'soy sauce', 'water'} + +# Overlapping members need to be removed in a separate step +# when there are more than two sets that need symmetric difference. +>>> (one ^ two ^ three ^ four) - intersections +... +{'barley malt','bell pepper','breadcrumbs', 'cashews','celeriac', + 'chickpea flour','cornstarch','fresh basil', 'garlic powder', + 'ginger','honey','lemon','lemon zest','mixed herbs','mushrooms', + 'oregano','parsley','red onion','red pepper flakes','rosemary', + 'silken tofu','smoked tofu','spaghetti','sunflower oil', 'tofu', + 'tomatoes','turmeric','vegetable oil','vegetable stock','yeast'} +``` + +[symmetric_difference]: https://en.wikipedia.org/wiki/Symmetric_difference +~~~~ + [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable +[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation +[operator]: https://www.computerhope.com/jargon/o/operator.htm +[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset +[type-set]: https://docs.python.org/3/library/stdtypes.html#set +[intersection]: https://www.mathgoodies.com/lessons/sets/intersection diff --git a/concepts/sets/introduction.md b/concepts/sets/introduction.md index b2eaddc8e6..4c264f0903 100644 --- a/concepts/sets/introduction.md +++ b/concepts/sets/introduction.md @@ -1,14 +1,28 @@ # Sets -A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects. -Items within a `set` are unique, and no duplicates are allowed. -Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable]. -Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor. +A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. +Set members must be distinct -- duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Sets also come in an immutable [`frozensets`][type-frozenset] flavor. -Like other collection types, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, & iteration via `for item in `. -_Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors. +Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. +They are also used for efficient comparisons when sequencing and duplicate tracking are not needed. -`sets` are most commonly used to quickly dedupe groups of items. +Like other collection types (_dictionaries, lists, tuples_), `sets` support: +- Iteration via `for item in ` +- Membership checking via `in` and `not in`, +- Length calculation through `len()`, and +- Shallow copies through `copy()` + +`sets` do not support: +- Indexing of any kind +- Ordering via sorting or insertion +- Slicing +- Concatenation via `+` + + +Checking membership in a `set` has constant time complexity (on average) versus checking membership in a `list` or `string`, where the time complexity grows as the length of the data increases. +Methods such as `.union()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). [type-set]: https://docs.python.org/3/library/stdtypes.html#set [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable diff --git a/exercises/concept/cater-waiter/.docs/hints.md b/exercises/concept/cater-waiter/.docs/hints.md index 056dcceecb..89f51753bc 100644 --- a/exercises/concept/cater-waiter/.docs/hints.md +++ b/exercises/concept/cater-waiter/.docs/hints.md @@ -3,14 +3,14 @@ ## General - [Sets][sets] are mutable, unordered collections with no duplicate elements. -- Sets can contain any data type, but all elements within a set must be [hashable][hashable]. +- Sets can contain any data type, as long as all elements are [hashable][hashable]. - Sets are [iterable][iterable]. - Sets are most often used to quickly dedupe other collections or for membership testing. - Sets also support mathematical operations like `union`, `intersection`, `difference`, and `symmetric difference` ## 1. Clean up Dish Ingredients -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. +- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. - Remember: [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. ## 2. Cocktails and Mocktails diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 905504a63b..235ae86937 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -1,291 +1,403 @@ # Sets -A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects. -Items within a `set` are distinct and duplicate members are not allowed. -Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable]. -Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor. +A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. +Set members must be distinct -- duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Sets also come in an immutable [`frozensets`][type-frozenset] flavor. -Like other collections, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, and iteration via `for item in `. -_Unlike_ sequence type collections (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors. +Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. +They are also used for efficient comparisons when sequencing and duplicate tracking are not needed. -Sets are most commonly used to quickly dedupe groups of items. -They're also used for fast membership testing, finding supersets & subsets of items, and performing "set math" (_calculating union, intersection, difference & symmetric difference between groups of items._). +Like other collection types (_dictionaries, lists, tuples_), `sets` support: +- Iteration via `for item in ` +- Membership checking via `in` and `not in`, +- Length calculation through `len()`, and +- Shallow copies through `copy()` -Sets are more space-efficient than a keys-only dictionary and faster than a `list` or `array` for membership -- unless you need to keep track of sequenced or duplicated items. +`sets` do not support: +- Indexing of any kind +- Ordering via sorting or insertion +- Slicing +- Concatenation via `+` -## Construction -A `set` can be declared as a _set literal_ with curly `{}` brackets and commas between elements. +Checking membership in a `set` has constant time complexity (on average) versus checking membership in a `list` or `string`, where the time complexity grows as the length of the data increases. +Methods such as `.union()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). + + +## Set Literals + +A `set` can be directly entered as a _set literal_ with curly `{}` brackets and commas between elements. +Duplicates are silently omitted: ```python >>> one_element = {'😀'} ->>> one_element {'😀'} >>> multiple_elements = {'😀', '😃', '😄', '😁'} ->>> multiple_elements {'😀', '😃', '😄', '😁'} ->>> multiple_duplicates = {'😀', '😃', '😄', '😁', '😃', '😄'} ->>> multiple_duplicates -{'😀', '😁', '😃', '😄'} +>>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!', + '¡Hola!','Привіт!', 'こんにちは!', + '¡Hola!','Привіт!', 'こんにちは!'} +{'こんにちは!', '¡Hola!', 'Hello!', 'Привіт!'} ``` -Set literals use the same curly braces as `dict` literals, so the `set()` constructor must be used to declare an empty `set`. +Set literals use the same curly braces as `dict` literals, which means you need to use `set()` to create an empty `set`. -The `set()` constructor can also be used with any _iterable_ passed as an argument. -Elements inside the iterable are cycled through by the constructor and added to the `set` individually. -Order is not preserved and duplicates are silently omitted: + +## The Set Constructor + +`set()` (_the constructor for the `set` class_) can be used with any `iterable` passed as an argument. +Elements of the `iterable` are cycled through and added to the `set` individually. +Element order is not preserved and duplicates are silently omitted: ```python +# To create an empty set, the constructor must be used. >>> no_elements = set() ->>> no_elements set() -# The tuple is unpacked and each distinct element is added. Duplicates are removed. ->>> multiple_elements_from_tuple = set(("Parrot", "Bird", 334782, "Bird", "Parrot")) ->>> multiple_elements_from_tuple +# The tuple is unpacked & each element is added. +# Duplicates are removed. +>>> elements_from_tuple = set(("Parrot", "Bird", + 334782, "Bird", "Parrot")) {334782, 'Bird', 'Parrot'} -# The list is unpacked and each distinct element is added. ->>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13]) ->>> multiple_elements_from_set +# The list is unpacked & each element is added. +# Duplicates are removed. +>>> elements_from_list = set([2, 3, 2, 3, 3, 3, 5, + 7, 11, 7, 11, 13, 13]) {2, 3, 5, 7, 11, 13} ``` -Sets can hold heterogeneous datatypes, but all `set` elements must be _hashable_: +### Gotchas when Creating Sets -```python +Due to its "unpacking" behavior, using `set()` with a string might be surprising: ->>> lists_as_elements = {['😅','🤣'], ['😂','🙂','🙃'], ['😜', '🤪', '😝']} +```python +# String elements (Unicode code points) are +# iterated through and added *individually*. +>>> elements_string = set("Timbuktu") +{'T', 'b', 'i', 'k', 'm', 't', 'u'} + +# Unicode separators and positioning code points +# are also added *individually*. +>>> multiple_code_points_string = set('अभ्यास') +{'अ', 'भ', 'य', 'स', 'ा', '्'} +``` -Traceback (most recent call last): +Sets can hold different datatypes and _nested_ datatypes, but all `set` elements must be _hashable_: - File "", line 1, in - lists_as_elements = {['😅','🤣'], ['😂','🙂','🙃'], ['😜', '🤪', '😝']} +```python +# Attempting to use a list for a set member throws a TypeError +>>> lists_as_elements = {['😅','🤣'], + ['😂','🙂','🙃'], + ['😜', '🤪', '😝']} +Traceback (most recent call last): + File "", line 1, in TypeError: unhashable type: 'list' -# Standard sets are mutable, so they cannot be hashed. ->>> sets_as_elements = {{'😅','🤣'}, {'😂','🙂','🙃'}, {'😜', '🤪', '😝'}} -Traceback (most recent call last): - File "", line 1, in - sets_as_elements = {{'😅','🤣'}, {'😂','🙂','🙃'}, {'😜', '🤪', '😝'}} +# Standard sets are mutable, so they cannot be hashed. +>>> sets_as_elements = {{'😅','🤣'}, + {'😂','🙂','🙃'}, + {'😜', '🤪', '😝'}} +Traceback (most recent call last): + File "", line 1, in TypeError: unhashable type: 'set' ``` -## Working with Sets -Sets implement methods that generally mimic [mathematical set operations][mathematical-sets]. -Most (_though not all_) of these methods can be performed using either operator(s) or method call(s). -Using operators requires that both inputs be `sets` or `frozensets`, while methods will generally take any iterable as an argument. +## Working with Sets -### Fast Membership Testing +Sets have methods that generally mimic [mathematical set operations][mathematical-sets]. +Most (_not all_) of these methods have an [operator][operator] equivalent. +Methods generally take any `iterable` as an argument, while operators require that both things being compared are `sets` or `frozensets`. -**Subsets**: `.issubset()` / ` <= ` - are used to check if every element in `` is also in ``. +### Disjoint Sets -**Supersets**: `.issuperset()` / ` >= ` - are used to check the inverse -- if every element in `` is also in ``. +The `.isdisjoint()` method is used to test if a `sets` elements have any overlap with the elements of another `set`. +The method will accept any `iterable` or `set` as an argument. +It will return `True` if the two sets have **no elements in common**, `False` if elements are **shared**. +There is no operator equivalent: ```python ->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white', - 'albatross': 'grey and white','crow': 'black','elephant': 'grey', - 'dog': 'rust','cow': 'black and white','tiger': 'organge and black', - 'cat': 'grey','squirrel': 'black'} - ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} - -# Methods will take any iterable as an argument ->>> mammals.issubset(animals) +# Mammals and birds don't share any elements. +>>> birds.isdisjoint(mammals) True -# A set is always a loose subset of itself ->>> birds <= birds -True - ->>> birds <= set(animals) +# There are also no shared elements between +# additional_animals and birds. +>>> birds.isdisjoint(additional_animals) True ->>> birds <= mammals +# Animals and mammals have shared elements. +# **Note** The first object needs to be a set or converted to a set +# since .isdisjoint() is a set method. +>>> set(animals).isdisjoint(mammals) False ``` -The `.isdisjoint()` method is used to test if a `set` has **no elements in common** with another set or iterable. -It will accept any `iterable` or `set` as an argument, returning `True` if they are **disjoint**, `False` otherwise. -Note that for `dicts`, the iteration default is over`.keys()`. +### Subsets and Supersets -```python ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} +`.issubset()` is used to check if every element in `` is also in ``. +The operator form is ` <= `: -# Dictionary of animal names with colors ->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white', - 'albatross': 'grey and white','crow': 'black','elephant': 'grey', - 'dog': 'rust','cow': 'black and white','tiger': 'orange and black', - 'cat': 'grey','squirrel': 'black'} -# List of additional animals ->>> additional_animals = ['pangolin', 'panda', 'parrot', 'lemur', 'tiger', 'pangolin'] -... +```python +# Both mammals and additional_animals are lists. +>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant'] +>>> additional_animals = ['pangolin', 'panda', 'parrot', + 'lemur', 'tiger', 'pangolin'] + +# Animals is a dict. +>>> animals = {'chicken': 'white', + 'sparrow': 'grey', + 'eagle': 'brown and white', + 'albatross': 'grey and white', + 'crow': 'black', + 'elephant': 'grey', + 'dog': 'rust', + 'cow': 'black and white', + 'tiger': 'orange and black', + 'cat': 'grey', + 'squirrel': 'black'} + +# Birds is a set. +>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} + +# Set methods will take any iterable as an argument. +# All members of birds are also members of animals. +>>> birds.issubset(animals) +True ->>> mammals.isdisjoint(birds) +# All members of mammals also appear in animals. +# **Note** The first object needs to be a set or converted to a set +# since .issubset() is a set method. +>>> set(mammals).issubset(animals) True ->>> mammals.isdisjoint(animals) +# Both objects need to be sets to use a set operator +>>> birds <= set(mammals) False ->>> birds.isdisjoint(additional_animals) +# A set is always a loose subset of itself. +>>> set(additional_animals) <= set(additional_animals) +True +``` + +`.issuperset()` is the inverse of `.issubset()`. +It is used to check if every element in `` is also in ``. +The operator form is ` >= `: + + +```python +# All members of mammals also appear in animals. +# **Note** The first object needs to be a set or converted to a set +# since .issuperset() is a set method. +>>> set(animals).issuperset(mammals) True ->>> set(additional_animals).isdisjoint(animals) +# All members of animals do not show up as members of birds. +>>> birds.issuperset(animals) False + +# Both objects need to be sets to use a set operator +>>> birds >= set(mammals) +False + +# A set is always a loose superset of itself. +>>> set(animals) <= set(animals) +True ``` -### Operations Between Sets -**Union**: `.union(*)` and ` | | | ... | ` return a new `set` with elements from `` and all ``. +### Set Intersections + +`.intersection(*)` returns a new `set` with elements common to the original `set` and all `` (_in other words, the `set` where everything [intersects][intersection]_). +The operator version of this method is ` & & & ... `: + ```python ->>> perennial_vegetables = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'} ->>> annual_vegetables = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'} +>>> perennials = {'Annatto','Asafetida','Asparagus','Azalea', + 'Winter Savory', 'Broccoli','Curry Leaf','Fennel', + 'Kaffir Lime','Kale','Lavender','Mint','Oranges', + 'Oregano', 'Tarragon', 'Wild Bergamot'} ->>> more_perennials = ['Radicchio', 'Rhubarb', 'Spinach', 'Watercress'] +>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', + 'Summer Squash', 'Okra','Shallots', 'Basil', + 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', + 'Summer Savory'} -# Methods will take any iterable as an argument. ->>> perennial_vegetables.union(more_perennials) -{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb','Spinach','Sweet Potato','Watercress'} +>>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro', + 'Curry Leaf','Fennel','Kaffir Lime','Lavender', + 'Marjoram','Mint','Oregano','Summer Savory' + 'Tarragon','Wild Bergamot','Wild Celery', + 'Winter Savory'] -# Operators require sets. ->>> perennial_vegetables | annual_vegetables -{'Asparagus','Broccoli','Corn','Kale','Summer Squash','Sweet Peas','Sweet Potato','Zucchini'} +# Methods will take any iterable as an argument. +>>> perennial_herbs = perennials.intersection(herbs) +{'Annatto', 'Asafetida', 'Curry Leaf', 'Fennel', 'Kaffir Lime', + 'Lavender', 'Mint', 'Oregano', 'Wild Bergamot','Winter Savory'} + +# Operators require both groups be sets. +>>> annuals & set(herbs) + {'Basil', 'Chervil', 'Marjoram', 'Cilantro'} ``` -**Difference**: `.difference(*)` and ` - - - ...` return a new `set` with elements from the original `` that are not in ``. + +### Set Unions + +`.union(*)` returns a new `set` with elements from `` and all ``. +The operator form of this method is ` | | | ... | `: + ```python ->>> berries_and_veggies = {'Asparagus', 'Broccoli', 'Watercress', 'Goji Berries', 'Goose Berries', 'Ramps', - 'Walking Onions', 'Raspberries','Blueberries', 'Blackberries', 'Strawberries', - 'Rhubarb', 'Kale', 'Artichokes', 'Currants', 'Honeyberries'} +>>> perennials = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'} +>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'} +>>> more_perennials = ['Radicchio', 'Rhubarb', + 'Spinach', 'Watercress'] # Methods will take any iterable as an argument. ->>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps', - 'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes') - ->>> just_berries = berries_and_veggies.difference(veggies) ->>> just_berries -{'Blackberries','Blueberries','Currants','Goji Berries', - 'Goose Berries','Honeyberries','Raspberries','Strawberries'} +>>> perennials.union(more_perennials) +{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb', +'Spinach','Sweet Potato','Watercress'} ->>> berries_and_veggies - just_berries -{'Artichokes','Asparagus','Broccoli','Kale','Ramps','Rhubarb','Walking Onions','Watercress'} +# Operators require sets. +>>> set(more_perennials) | perennials +{'Asparagus', + 'Broccoli', + 'Kale', + 'Radicchio', + 'Rhubarb', + 'Spinach', + 'Sweet Potato', + 'Watercress'} ``` -**Intersection**: `.intersection(*)` and ` & & & ... ` return a new `set` with elements common to the original `set` and all ``. -```python ->>> perennials = {'Annatto','Asafetida','Asparagus','Azalea','Winter Savory', 'Blackberries','Broccoli','Curry Leaf', - 'Fennel','French Sorrel','Fuchsia','Kaffir Lime','Kale','Lavender','Mint','Oranges', - 'Oregano','Ramps','Roses','Tarragon','Watercress','Wild Bergamot'} +### Set Differences ->>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', 'Summer Squash', 'Okra', - 'Shallots', 'Basil', 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', 'Summer Savory'} +`.difference(*)` returns a new `set` with elements from the original `` that are not in ``. +The operator version of this method is ` - - - ...`. ->>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro','Curry Leaf','Fennel','Kaffir Lime', - 'Lavender','Marjoram','Mint','Oregano','Summer Savory' 'Tarragon','Wild Bergamot', - 'Wild Celery','Winter Savory'] +```python +>>> berries_and_veggies = {'Asparagus', + 'Broccoli', + 'Watercress', + 'Goji Berries', + 'Goose Berries', + 'Ramps', + 'Walking Onions', + 'Blackberries', + 'Strawberries', + 'Rhubarb', + 'Kale', + 'Artichokes', + 'Currants'} +>>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps', + 'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes') # Methods will take any iterable as an argument. ->>> perennial_herbs = perennials.intersection(herbs) ->>> perennial_herbs -{'Mint', 'Annatto', 'Winter Savory', 'Curry Leaf', 'Lavender', 'Fennel', - 'Oregano', 'Kaffir Lime','Asafetida', 'Wild Bergamot', 'Tarragon'} +>>> berries = berries_and_veggies.difference(veggies) +{'Blackberries','Currants','Goji Berries', + 'Goose Berries', 'Strawberries'} ->>> annuals & set(herbs) - {'Basil', 'Chervil', 'Marjoram', 'Cilantro'} +# Operators require sets. +>>> berries_and_veggies - just_berries +{'Artichokes','Asparagus','Broccoli','Kale', +'Ramps','Rhubarb','Walking Onions','Watercress'} ``` -**Symmetric Difference**: `.symmetric_difference()` and ` ^ ` return a new `set` that contains elements that are in `` OR ``, but **not in both**. -```python ->>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour', - 'flour','lemon','parsley','salt','soy sauce','sunflower oil','water'} +# Set Symmetric Difference ->>> two = {'black pepper','cornstarch','garlic','ginger','lemon juice','lemon zest', - 'salt','soy sauce','sugar','tofu','vegetable oil','vegetable stock','water'} +`.symmetric_difference()` returns a new `set` that contains elements that are in `` OR ``, **but not in both**. +The operator version of this method is ` ^ `: ->>> two_as_list = ['black pepper','cornstarch','garlic','ginger','lemon juice','lemon zest', - 'salt','soy sauce','sugar','tofu','vegetable oil','vegetable stock','water'] ->>> one ^ two -... -{'breadcrumbs','celeriac','chickpea flour','cornstarch','flour','garlic','ginger', 'lemon', -'lemon juice','lemon zest','parsley','sugar','sunflower oil','tofu','vegetable oil','vegetable stock'} +```python +>>> plants_1 = {'🌲','🍈','🌵', '🥑','🌴', '🥭'} +>>> plants_2 = ('🌸','🌴', '🌺', '🌲', '🌻', '🌵') ->>> (one | two) - (one & two) -... -{'breadcrumbs','celeriac','chickpea flour','cornstarch','flour','garlic','ginger', 'lemon', -'lemon juice','lemon zest','parsley','sugar','sunflower oil','tofu','vegetable oil','vegetable stock'} ->>> one ^ two == (one | two) - (one & two) -... -True +# Methods will take any iterable as an argument. +>>> fruit_and_flowers = plants_1.symmetric_difference(plants_2) +>>> fruit_and_flowers +{'🌸', '🌺', '🍈', '🥑', '🥭','🌻' } -# Methods will take any iterable as an argument. ->>> one.symmetric_difference(two_as_list) -... -{'breadcrumbs','celeriac','chickpea flour','cornstarch','flour','garlic','ginger', 'lemon', -'lemon juice','lemon zest','parsley','sugar','sunflower oil','tofu','vegetable oil','vegetable stock'} +# Operators require both groups be sets. +>>> fruit_and_flowers ^ plants_1 +{'🌲', '🌸', '🌴', '🌵','🌺', '🌻'} + +>>> fruit_and_flowers ^ plants_2 +{ '🥑', '🌴','🌲', '🌵', '🍈', '🥭'} ``` -A symmetric difference of more than two sets will result in a `set` that includes both the elements unique to each `set` AND elements shared between more than two sets in the series (_details in the Wikipedia article on [symmetric difference][symmetric_difference]_). -To obtain only items unique to each `set` in the series, intersections between all 2-set combinations need to be aggregated in a separate step, and removed. +~~~~exercism/note + +A symmetric difference of more than two sets will result in a `set` that includes both the elements unique to each `set` AND elements shared between more than two sets in the series (_details in the Wikipedia article on [symmetric difference][symmetric_difference]_). + +To obtain only items unique to each `set` in the series, intersections between all 2-set combinations need to be aggregated in a separate step, and removed: + ```python >>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour', - 'flour','lemon','parsley','salt','soy sauce','sunflower oil','water'} - ->>> two = {'black pepper','cornstarch','garlic','ginger','lemon juice','lemon zest', - 'salt','soy sauce','sugar','tofu','vegetable oil','vegetable stock','water'} + 'flour','lemon','parsley','salt','soy sauce', + 'sunflower oil','water'} ->>> three = {'black pepper','garlic','lemon juice','mixed herbs','nutritional yeast', - 'olive oil','salt','silken tofu','smoked tofu','soy sauce','spaghetti','turmeric'} +>>> two = {'black pepper','cornstarch','garlic','ginger', + 'lemon juice','lemon zest','salt','soy sauce','sugar', + 'tofu','vegetable oil','vegetable stock','water'} ->>> four = {'barley malt','bell pepper','cashews','flour','fresh basil','garlic','garlic powder', - 'honey','mushrooms','nutritional yeast','olive oil','oregano','red onion', - 'red pepper flakes','rosemary','salt','sugar','tomatoes','water','yeast'} +>>> three = {'black pepper','garlic','lemon juice','mixed herbs', + 'nutritional yeast', 'olive oil','salt','silken tofu', + 'smoked tofu','soy sauce','spaghetti','turmeric'} ->>> intersections = (one & two | one & three | one & four | two & three | two & four | three & four) ->>> intersections - ... - {'black pepper','flour','garlic','lemon juice','nutritional yeast', 'olive oil','salt','soy sauce', 'sugar','water'} +>>> four = {'barley malt','bell pepper','cashews','flour', + 'fresh basil','garlic','garlic powder', 'honey', + 'mushrooms','nutritional yeast','olive oil','oregano', + 'red onion', 'red pepper flakes','rosemary','salt', + 'sugar','tomatoes','water','yeast'} ->>> one ^ two ^ three ^ four +>>> intersections = (one & two | one & three | one & four | + two & three | two & four | three & four) ... -{'barley malt','bell pepper','black pepper','breadcrumbs','cashews','celeriac','chickpea flour','cornstarch', - 'fresh basil','garlic','garlic powder','ginger','honey','lemon','lemon zest','mixed herbs','mushrooms', - 'oregano','parsley','red onion','red pepper flakes','rosemary','silken tofu','smoked tofu','soy sauce', - 'spaghetti','sunflower oil','tofu','tomatoes','turmeric','vegetable oil','vegetable stock','water','yeast'} +{'black pepper','flour','garlic','lemon juice','nutritional yeast', +'olive oil','salt','soy sauce', 'sugar','water'} + +# The ^ operation will include some of the items in intersections, +# which means it is not a "clean" symmetric difference - there +# are overlapping members. +>>> (one ^ two ^ three ^ four) & intersections +{'black pepper', 'garlic', 'soy sauce', 'water'} +# Overlapping members need to be removed in a separate step +# when there are more than two sets that need symmetric difference. >>> (one ^ two ^ three ^ four) - intersections ... -{'barley malt','bell pepper','breadcrumbs', 'cashews','celeriac','chickpea flour','cornstarch','fresh basil', - 'garlic powder','ginger','honey','lemon','lemon zest','mixed herbs','mushrooms','oregano','parsley', - 'red onion','red pepper flakes','rosemary','silken tofu','smoked tofu','spaghetti','sunflower oil', - 'tofu', 'tomatoes','turmeric','vegetable oil','vegetable stock','yeast'} +{'barley malt','bell pepper','breadcrumbs', 'cashews','celeriac', + 'chickpea flour','cornstarch','fresh basil', 'garlic powder', + 'ginger','honey','lemon','lemon zest','mixed herbs','mushrooms', + 'oregano','parsley','red onion','red pepper flakes','rosemary', + 'silken tofu','smoked tofu','spaghetti','sunflower oil', 'tofu', + 'tomatoes','turmeric','vegetable oil','vegetable stock','yeast'} ``` [symmetric_difference]: https://en.wikipedia.org/wiki/Symmetric_difference -[type-set]: https://docs.python.org/3/library/stdtypes.html#set -[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset -[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation +~~~~ + [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable +[intersection]: https://www.mathgoodies.com/lessons/sets/intersection +[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation +[operator]: https://www.computerhope.com/jargon/o/operator.htm +[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset +[type-set]: https://docs.python.org/3/library/stdtypes.html#set diff --git a/exercises/concept/cater-waiter/sets_test.py b/exercises/concept/cater-waiter/sets_test.py index de35ecad1d..ec93507ae6 100644 --- a/exercises/concept/cater-waiter/sets_test.py +++ b/exercises/concept/cater-waiter/sets_test.py @@ -48,8 +48,8 @@ def test_clean_ingredients(self): with self.subTest(f"variation #{variant}", inputs="recipes with duplicated ingredients", result="recipe ingredients de-duped"): - error_msg = (f"Expected a cleaned ingredient list for {item[0]}, " - "but the ingredients aren't cleaned as expected.") + error_msg = (f"Expected the ingredient list for {item[0]} to be de-duplicated, " + "but the ingredients were not cleaned as expected.") self.assertEqual(clean_ingredients(item[0], item[1]), (result[1], result[2]), msg=error_msg) From ad890c134ca3ec136ba5421675a4f3dcb10c4a96 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 18 Oct 2023 13:12:00 +0200 Subject: [PATCH 527/826] Fix grammar in currency-exchange exercise (#3517) The word _amount_ is generally used for things that come in piles (like money) or that can otherwise be measured but not counted (like water). The word _number_ is typically used if you can count them. Technically money can be counted, but it's an exception (see 'piles' above). Here we count bills and digits, so 'number' is the more common choice of word. --- exercises/concept/currency-exchange/.docs/instructions.md | 2 +- exercises/concept/currency-exchange/.docs/introduction.md | 2 +- exercises/concept/currency-exchange/.meta/exemplar.py | 2 +- exercises/concept/currency-exchange/exchange.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/concept/currency-exchange/.docs/instructions.md b/exercises/concept/currency-exchange/.docs/instructions.md index 3bb6bff971..adce534931 100644 --- a/exercises/concept/currency-exchange/.docs/instructions.md +++ b/exercises/concept/currency-exchange/.docs/instructions.md @@ -36,7 +36,7 @@ This function should return the amount of money that *is left* from the budget. Create the `get_value_of_bills()` function, taking 2 parameters: 1. `denomination` : The value of a single bill. -2. `number_of_bills` : Amount of bills you received. +2. `number_of_bills` : Number of bills you received. This exchanging booth only deals in cash of certain increments. The total you receive must be divisible by the value of one "bill" or unit, which can leave behind a fraction or remainder. diff --git a/exercises/concept/currency-exchange/.docs/introduction.md b/exercises/concept/currency-exchange/.docs/introduction.md index 231a6732aa..fe463ffa74 100644 --- a/exercises/concept/currency-exchange/.docs/introduction.md +++ b/exercises/concept/currency-exchange/.docs/introduction.md @@ -8,7 +8,7 @@ There are three different kinds of built-in numbers in Python : `ints`, `floats` `ints` are whole numbers. e.g. `1234`, `-10`, `20201278`. -Integers in Python have [arbitrary precision][arbitrary-precision] -- the amount of digits is limited only by the available memory of the host system. +Integers in Python have [arbitrary precision][arbitrary-precision] -- the number of digits is limited only by the available memory of the host system. ### floats diff --git a/exercises/concept/currency-exchange/.meta/exemplar.py b/exercises/concept/currency-exchange/.meta/exemplar.py index c894f2c045..99ba5d2626 100644 --- a/exercises/concept/currency-exchange/.meta/exemplar.py +++ b/exercises/concept/currency-exchange/.meta/exemplar.py @@ -24,7 +24,7 @@ def get_value_of_bills(denomination, number_of_bills): """ :param denomination: int - the value of a bill. - :param number_of_bills: int - amount of bills you received. + :param number_of_bills: int - number of bills you received. :return: int - total value of bills you now have. """ diff --git a/exercises/concept/currency-exchange/exchange.py b/exercises/concept/currency-exchange/exchange.py index 5b54fea4aa..33454dc54a 100644 --- a/exercises/concept/currency-exchange/exchange.py +++ b/exercises/concept/currency-exchange/exchange.py @@ -24,7 +24,7 @@ def get_value_of_bills(denomination, number_of_bills): """ :param denomination: int - the value of a bill. - :param number_of_bills: int - amount of bills you received. + :param number_of_bills: int - number of bills you received. :return: int - total value of bills you now have. """ From 95f25cf279ed2c5cb3256484cfae6d6bceff337c Mon Sep 17 00:00:00 2001 From: Christian Willner <34183939+vaeng@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:50:52 +0200 Subject: [PATCH 528/826] feat: add pop-count exercise (#3518) * feat: add pop-count practice exercise * config: del house, protein-trans from syllabus As pop-count was added to the loop concept, house and protein translation were removed to keep the tree lean. * Update config.json On second (and third) thought, let's put pop-count first -- ahead of saddle points and OCR. Feels like a better progression that way. --------- Co-authored-by: BethanyG --- config.json | 19 +++++++- .../practice/pop-count/.docs/instructions.md | 8 ++++ .../practice/pop-count/.docs/introduction.md | 47 +++++++++++++++++++ .../practice/pop-count/.meta/config.json | 17 +++++++ exercises/practice/pop-count/.meta/example.py | 6 +++ .../practice/pop-count/.meta/template.j2 | 14 ++++++ exercises/practice/pop-count/.meta/tests.toml | 22 +++++++++ exercises/practice/pop-count/pop_count.py | 2 + .../practice/pop-count/pop_count_test.py | 27 +++++++++++ 9 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 exercises/practice/pop-count/.docs/instructions.md create mode 100644 exercises/practice/pop-count/.docs/introduction.md create mode 100644 exercises/practice/pop-count/.meta/config.json create mode 100644 exercises/practice/pop-count/.meta/example.py create mode 100644 exercises/practice/pop-count/.meta/template.j2 create mode 100644 exercises/practice/pop-count/.meta/tests.toml create mode 100644 exercises/practice/pop-count/pop_count.py create mode 100644 exercises/practice/pop-count/pop_count_test.py diff --git a/config.json b/config.json index 871f4c8429..37b140d2f6 100644 --- a/config.json +++ b/config.json @@ -466,7 +466,7 @@ "slug": "house", "name": "House", "uuid": "7c2e93ae-d265-4481-b583-a496608c6031", - "practices": ["loops"], + "practices": [], "prerequisites": [ "basics", "lists", @@ -680,7 +680,7 @@ "slug": "protein-translation", "name": "Protein Translation", "uuid": "c89243f3-703e-4fe0-8e43-f200eedf2825", - "practices": ["loops"], + "practices": [], "prerequisites": [ "basics", "conditionals", @@ -968,6 +968,21 @@ "prerequisites": ["basics", "bools", "numbers", "classes"], "difficulty": 2 }, + { + "slug": "pop-count", + "name": "Pop Count", + "uuid": "356e2d29-7efc-4fa3-bec7-8b61c3e967da", + "practices": ["loops"], + "prerequisites": [ + "basics", + "lists", + "list-methods", + "loops", + "strings", + "string-methods" + ], + "difficulty": 3 + }, { "slug": "saddle-points", "name": "Saddle Points", diff --git a/exercises/practice/pop-count/.docs/instructions.md b/exercises/practice/pop-count/.docs/instructions.md new file mode 100644 index 0000000000..b0c2df593c --- /dev/null +++ b/exercises/practice/pop-count/.docs/instructions.md @@ -0,0 +1,8 @@ +# Instructions + +Your task is to count the number of 1 bits in the binary representation of a number. + +## Restrictions + +Keep your hands off that bit-count functionality provided by your standard library! +Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/pop-count/.docs/introduction.md b/exercises/practice/pop-count/.docs/introduction.md new file mode 100644 index 0000000000..49eaffd8bc --- /dev/null +++ b/exercises/practice/pop-count/.docs/introduction.md @@ -0,0 +1,47 @@ +# Introduction + +Your friend Eliud inherited a farm from her grandma Tigist. +Her granny was an inventor and had a tendency to build things in an overly complicated manner. +The chicken coop has a digital display showing an encoded number representing the positions of all eggs that could be picked up. + +Eliud is asking you to write a program that shows the actual number of eggs in the coop. + +The position information encoding is calculated as follows: + +1. Scan the potential egg-laying spots and mark down a `1` for an existing egg or a `0` for an empty spot. +2. Convert the number from binary to decimal. +3. Show the result on the display. + +Example 1: + +```text +Chicken Coop: + _ _ _ _ _ _ _ +|E| |E|E| | |E| + +Resulting Binary: + 1 0 1 1 0 0 1 + +Decimal number on the display: +89 + +Actual eggs in the coop: +4 +``` + +Example 2: + +```text +Chicken Coop: + _ _ _ _ _ _ _ _ +| | | |E| | | | | + +Resulting Binary: + 0 0 0 1 0 0 0 0 + +Decimal number on the display: +16 + +Actual eggs in the coop: +1 +``` diff --git a/exercises/practice/pop-count/.meta/config.json b/exercises/practice/pop-count/.meta/config.json new file mode 100644 index 0000000000..baf822a16a --- /dev/null +++ b/exercises/practice/pop-count/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": ["vaeng"], + "files": { + "solution": [ + "pop_count.py" + ], + "test": [ + "pop_count_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Count the 1 bits in a number", + "source": "Christian Willner, Eric Willigers", + "source_url": "https://forum.exercism.org/t/new-exercise-suggestion-pop-count/7632/5" +} diff --git a/exercises/practice/pop-count/.meta/example.py b/exercises/practice/pop-count/.meta/example.py new file mode 100644 index 0000000000..4e3320aee9 --- /dev/null +++ b/exercises/practice/pop-count/.meta/example.py @@ -0,0 +1,6 @@ +def egg_count(display_value): + eggs = 0 + while display_value: + eggs += display_value % 2 + display_value //= 2 + return eggs diff --git a/exercises/practice/pop-count/.meta/template.j2 b/exercises/practice/pop-count/.meta/template.j2 new file mode 100644 index 0000000000..11c9e487d3 --- /dev/null +++ b/exercises/practice/pop-count/.meta/template.j2 @@ -0,0 +1,14 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header() }} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + def test_{{ case["description"] | to_snake }}(self): + expected = {{ case["expected"] }} + self.assertEqual( + {{ case["property"] | to_snake }}({{ case["input"]["number"] }}), expected + ) + + {% endfor %} \ No newline at end of file diff --git a/exercises/practice/pop-count/.meta/tests.toml b/exercises/practice/pop-count/.meta/tests.toml new file mode 100644 index 0000000000..e11683c2ef --- /dev/null +++ b/exercises/practice/pop-count/.meta/tests.toml @@ -0,0 +1,22 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[559e789d-07d1-4422-9004-3b699f83bca3] +description = "0 eggs" + +[97223282-f71e-490c-92f0-b3ec9e275aba] +description = "1 egg" + +[1f8fd18f-26e9-4144-9a0e-57cdfc4f4ff5] +description = "4 eggs" + +[0c18be92-a498-4ef2-bcbb-28ac4b06cb81] +description = "13 eggs" diff --git a/exercises/practice/pop-count/pop_count.py b/exercises/practice/pop-count/pop_count.py new file mode 100644 index 0000000000..bc88553850 --- /dev/null +++ b/exercises/practice/pop-count/pop_count.py @@ -0,0 +1,2 @@ +def egg_count(display_value): + pass diff --git a/exercises/practice/pop-count/pop_count_test.py b/exercises/practice/pop-count/pop_count_test.py new file mode 100644 index 0000000000..c3ca192156 --- /dev/null +++ b/exercises/practice/pop-count/pop_count_test.py @@ -0,0 +1,27 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pop-count/canonical-data.json +# File last updated on 2023-10-18 + +import unittest + +from pop_count import ( + egg_count, +) + + +class PopCountTest(unittest.TestCase): + def test_0_eggs(self): + expected = 0 + self.assertEqual(egg_count(0), expected) + + def test_1_egg(self): + expected = 1 + self.assertEqual(egg_count(16), expected) + + def test_4_eggs(self): + expected = 4 + self.assertEqual(egg_count(89), expected) + + def test_13_eggs(self): + expected = 13 + self.assertEqual(egg_count(2000000000), expected) From c47870afa6f4116548d5f723fcafef4fd2ffec2c Mon Sep 17 00:00:00 2001 From: Mett Mamoang <148342557+LittleMangoSeed@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:03:26 +0200 Subject: [PATCH 529/826] Change Thai in string methods concept and exercise (#3521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Little Sister's Essay concept and associated concept gives some example sentences using the phrase 'the man in the hat'. In English this is an idiomatic phrase that means the man who is wearing the hat, but could technically also translate to the man who is inside the hat. The Thai phrase ผู้ชายในหมวก translates the second meaning. I've replaced this with a phrase that better matches the intended meaning. This change was discussed in the following forum thread: https://forum.exercism.org/t/the-man-inside-the-hat-little-sisters-essay/7828/2 Co-authored-by: Mett Mamoang --- concepts/string-methods/about.md | 4 ++-- exercises/concept/little-sisters-essay/.docs/introduction.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/concepts/string-methods/about.md b/concepts/string-methods/about.md index e01cbaf890..9ca16ffa29 100644 --- a/concepts/string-methods/about.md +++ b/concepts/string-methods/about.md @@ -41,13 +41,13 @@ There may also be [locale][locale] rules in place for a language or character se ```python ->>> man_in_hat_th = 'ู้ชายในหมวก' +>>> man_in_hat_th = 'ผู้ชายใส่หมวก' >>> man_in_hat_ru = 'mужчина в шляпе' >>> man_in_hat_ko = '모자를 쓴 남자' >>> man_in_hat_en = 'the man in the hat.' >>> man_in_hat_th.title() -'ผู้ชายในหมวก' +'ผู้ชายใส่หมวก' >>> man_in_hat_ru.title() 'Мужчина В Шляпе' diff --git a/exercises/concept/little-sisters-essay/.docs/introduction.md b/exercises/concept/little-sisters-essay/.docs/introduction.md index 24358f2fc2..204c893e92 100644 --- a/exercises/concept/little-sisters-essay/.docs/introduction.md +++ b/exercises/concept/little-sisters-essay/.docs/introduction.md @@ -21,13 +21,13 @@ There may also be [locale][locale] rules in place for a language or character se ```python -man_in_hat_th = 'ู้ชายในหมวก' +man_in_hat_th = 'ผู้ชายใส่หมวก' man_in_hat_ru = 'mужчина в шляпе' man_in_hat_ko = '모자를 쓴 남자' man_in_hat_en = 'the man in the hat.' >>> man_in_hat_th.title() -'ผู้ชายในหมวก' +'ผู้ชายใส่หมวก' >>> man_in_hat_ru.title() 'Мужчина В Шляпе' From 8c30b562fc266833aaf660fb672d4a43984d3b6a Mon Sep 17 00:00:00 2001 From: Mett Mamoang <148342557+LittleMangoSeed@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:01:27 +0200 Subject: [PATCH 530/826] Clarify bills in currency exchange exercise (#3520) * Clarify bills in currency exchange exercise The exchange booth in this exercise only deals in cash of specific increments. That is to say, you can bring any combination of bills and coins to exchange, but you get a collection of bills of a single denomination in the target currency in return. The explanation was quite clear on this, but the parameter names and docstrings in the methods dealing with this part of the task used the term 'budget', which could lead readers to assume that the bills had to do with the original currency. This reworks the methods related to bills to be more generic, not referring to the exchange process at all, hopefully avoiding this misinterpretation. See forum thread here for details of the discussion leading to this change: https://forum.exercism.org/t/currency-exchange-exercise-suggested-naming-tweaks/7790 * Small Fixes from CI Fails Fixed some small argument name errors that were causing the CI to fail. Also sorted reflinks and touched up some language int he `hints.md` file and the instructions. [no important files changed] --------- Co-authored-by: Mett Mamoang Co-authored-by: BethanyG --- .../concept/currency-exchange/.docs/hints.md | 14 ++++++------- .../currency-exchange/.docs/instructions.md | 13 ++++++------ .../currency-exchange/.docs/introduction.md | 8 ++++---- .../currency-exchange/.meta/exemplar.py | 20 +++++++++---------- .../concept/currency-exchange/exchange.py | 16 +++++++-------- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/exercises/concept/currency-exchange/.docs/hints.md b/exercises/concept/currency-exchange/.docs/hints.md index b54f0d345f..fb27ff7761 100644 --- a/exercises/concept/currency-exchange/.docs/hints.md +++ b/exercises/concept/currency-exchange/.docs/hints.md @@ -18,26 +18,26 @@ ## 4. Calculate number of bills -- You need to divide `budget` into `denomination`. -- You need to use type casting to _int_ to get the exact number of bills. +- You need to divide `amount` into `denomination`. +- You need to use type casting to `int` to get the exact number of bills. - To remove decimal places from a `float`, you can convert it to `int`. **Note:** The `//` operator also does floor division. But, if the operand has `float`, the result is still `float`. ## 5. Calculate leftover after exchanging into bills -- You need to find the remainder of `budget` that does not equal a whole `denomination`. +- You need to find the remainder of `amount` that does not equal a whole `denomination`. - The Modulo operator `%` can help find the remainder. ## 6. Calculate value after exchange - You need to calculate `spread` percent of `exchange_rate` using multiplication operator and add it to `exchange_rate` to get the exchanged currency. -- The actual rate needs to be computed. Remember to add exchange rate and exchange fee. -- You can get exchanged money affected by commission by using divide operation and type casting _int_. +- The actual rate needs to be computed. Remember to add exchange _rate_ and exchange _fee_. +- You can get exchanged money affected by commission by using divide operation and type casting to `int`. +[division-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers +[multiplication-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers [python-numbers-tutorial]: https://docs.python.org/3/tutorial/introduction.html#numbers [python-numeric-types]: https://docs.python.org/3.9/library/stdtypes.html#numeric-types-int-float-complex -[division-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers [subtraction-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers -[multiplication-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers diff --git a/exercises/concept/currency-exchange/.docs/instructions.md b/exercises/concept/currency-exchange/.docs/instructions.md index adce534931..7a103d4df6 100644 --- a/exercises/concept/currency-exchange/.docs/instructions.md +++ b/exercises/concept/currency-exchange/.docs/instructions.md @@ -12,6 +12,7 @@ Create the `exchange_money()` function, taking 2 parameters: This function should return the value of the exchanged currency. **Note:** If your currency is USD and you want to exchange USD for EUR with an exchange rate of `1.20`, then `1.20 USD == 1 EUR`. + ```python >>> exchange_money(127.5, 1.2) 106.25 @@ -36,7 +37,7 @@ This function should return the amount of money that *is left* from the budget. Create the `get_value_of_bills()` function, taking 2 parameters: 1. `denomination` : The value of a single bill. -2. `number_of_bills` : Number of bills you received. +2. `number_of_bills` : The total number of bills. This exchanging booth only deals in cash of certain increments. The total you receive must be divisible by the value of one "bill" or unit, which can leave behind a fraction or remainder. @@ -50,10 +51,10 @@ Unfortunately, the booth gets to keep the remainder/change as an added bonus. ## 4. Calculate number of bills -Create the `get_number_of_bills()` function, taking `budget` and `denomination`. +Create the `get_number_of_bills()` function, taking `amount` and `denomination`. -This function should return the _number of currency bills_ that you can receive within the given _budget_. -In other words: How many _whole bills_ of currency fit into the amount of currency you have in your budget? +This function should return the _number of currency bills_ that you can receive within the given _amount_. +In other words: How many _whole bills_ of currency fit into the starting amount? Remember -- you can only receive _whole bills_, not fractions of bills, so remember to divide accordingly. Effectively, you are rounding _down_ to the nearest whole bill/denomination. @@ -64,9 +65,9 @@ Effectively, you are rounding _down_ to the nearest whole bill/denomination. ## 5. Calculate leftover after exchanging into bills -Create the `get_leftover_of_bills()` function, taking `budget` and `denomination`. +Create the `get_leftover_of_bills()` function, taking `amount` and `denomination`. -This function should return the _leftover amount_ that cannot be exchanged from your _budget_ given the denomination of bills. +This function should return the _leftover amount_ that cannot be returned from your starting _amount_ given the denomination of bills. It is very important to know exactly how much the booth gets to keep. ```python diff --git a/exercises/concept/currency-exchange/.docs/introduction.md b/exercises/concept/currency-exchange/.docs/introduction.md index fe463ffa74..c5d3d5caa6 100644 --- a/exercises/concept/currency-exchange/.docs/introduction.md +++ b/exercises/concept/currency-exchange/.docs/introduction.md @@ -70,9 +70,9 @@ To convert a float to an integer, you can use `int()`. Also, to convert an integ 3.0 ``` -[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic#:~:text=In%20computer%20science%2C%20arbitrary%2Dprecision,memory%20of%20the%20host%20system. -[numeric-type-docs]: https://docs.python.org/3/library/stdtypes.html#typesnumeric -[`int()` built in]: https://docs.python.org/3/library/functions.html#int -[`float()` built in]: https://docs.python.org/3/library/functions.html#float [0.30000000000000004.com]: https://0.30000000000000004.com/ +[`float()` built in]: https://docs.python.org/3/library/functions.html#float +[`int()` built in]: https://docs.python.org/3/library/functions.html#int +[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic#:~:text=In%20computer%20science%2C%20arbitrary%2Dprecision,memory%20of%20the%20host%20system. [floating point math]: https://docs.python.org/3.9/tutorial/floatingpoint.html +[numeric-type-docs]: https://docs.python.org/3/library/stdtypes.html#typesnumeric diff --git a/exercises/concept/currency-exchange/.meta/exemplar.py b/exercises/concept/currency-exchange/.meta/exemplar.py index 99ba5d2626..e452818f24 100644 --- a/exercises/concept/currency-exchange/.meta/exemplar.py +++ b/exercises/concept/currency-exchange/.meta/exemplar.py @@ -24,33 +24,33 @@ def get_value_of_bills(denomination, number_of_bills): """ :param denomination: int - the value of a bill. - :param number_of_bills: int - number of bills you received. - :return: int - total value of bills you now have. + :param number_of_bills: int - total number of bills. + :return: int - calculated value of the bills. """ return denomination * number_of_bills -def get_number_of_bills(budget, denomination): +def get_number_of_bills(amount, denomination): """ - :param budget: float - the amount of money you are planning to exchange. + :param amount: float - the total starting value. :param denomination: int - the value of a single bill. - :return: int - number of bills after exchanging all your money. + :return: int - number of bills that can be obtained from the amount. """ - return int(budget) // denomination + return int(amount) // denomination -def get_leftover_of_bills(budget, denomination): +def get_leftover_of_bills(amount, denomination): """ - :param budget: float - the amount of money you are planning to exchange. + :param amount: float - the total starting value. :param denomination: int - the value of a single bill. - :return: float - the leftover amount that cannot be exchanged given the current denomination. + :return: float - the amount that is "leftover", given the current denomination. """ - return budget % denomination + return amount % denomination def exchangeable_value(budget, exchange_rate, spread, denomination): diff --git a/exercises/concept/currency-exchange/exchange.py b/exercises/concept/currency-exchange/exchange.py index 33454dc54a..e05c7bcc54 100644 --- a/exercises/concept/currency-exchange/exchange.py +++ b/exercises/concept/currency-exchange/exchange.py @@ -24,30 +24,30 @@ def get_value_of_bills(denomination, number_of_bills): """ :param denomination: int - the value of a bill. - :param number_of_bills: int - number of bills you received. - :return: int - total value of bills you now have. + :param number_of_bills: int - total number of bills. + :return: int - calculated value of the bills. """ pass -def get_number_of_bills(budget, denomination): +def get_number_of_bills(amount, denomination): """ - :param budget: float - the amount of money you are planning to exchange. + :param amount: float - the total starting value. :param denomination: int - the value of a single bill. - :return: int - number of bills after exchanging all your money. + :return: int - number of bills that can be obtained from the amount. """ pass -def get_leftover_of_bills(budget, denomination): +def get_leftover_of_bills(amount, denomination): """ - :param budget: float - the amount of money you are planning to exchange. + :param amount: float - the total starting value. :param denomination: int - the value of a single bill. - :return: float - the leftover amount that cannot be exchanged given the current denomination. + :return: float - the amount that is "leftover", given the current denomination. """ pass From 358e406e5a7a3c6b37fabda9bd2a867d0200dd4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 17:06:47 -0700 Subject: [PATCH 531/826] Bump exercism/pr-commenter-action from 1.5.0 to 1.5.1 (#3525) Bumps [exercism/pr-commenter-action](https://github.com/exercism/pr-commenter-action) from 1.5.0 to 1.5.1. - [Release notes](https://github.com/exercism/pr-commenter-action/releases) - [Changelog](https://github.com/exercism/pr-commenter-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/exercism/pr-commenter-action/compare/v1.5.0...v1.5.1) --- updated-dependencies: - dependency-name: exercism/pr-commenter-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr-commenter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index 0bf1662006..d8cc693906 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -6,7 +6,7 @@ jobs: pr-comment: runs-on: ubuntu-latest steps: - - uses: exercism/pr-commenter-action@v1.5.0 + - uses: exercism/pr-commenter-action@v1.5.1 with: github-token: "${{ github.token }}" config-file: ".github/pr-commenter.yml" \ No newline at end of file From 166a0d803e808e8ea7df183517e544400b1bd2c8 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Fri, 27 Oct 2023 22:30:30 +0200 Subject: [PATCH 532/826] Change 'amount' to 'number' for countables (#3526) This is a follow-up to https://github.com/exercism/python/pull/3517 I've fixed the following occurrences: - number of values - number of digits - number of function calls - number of hours - number of health points - number of (intermediate) delivery points - number of keyword arguments - number of grains - number of names I have also verified that the following are correct: - amount of reputation - amount of work - amount of functionality - amount of memory - amount of money - amount of domestic currency - amount of tree climbing - amount of energy [no important files changed] --- concepts/function-arguments/about.md | 12 ++++++------ concepts/numbers/about.md | 2 +- concepts/recursion/about.md | 2 +- concepts/recursion/introduction.md | 2 +- .../concept/electric-bill/.docs/introduction.md | 2 +- exercises/concept/electric-bill/.meta/exemplar.py | 6 +++--- exercises/concept/electric-bill/electric_bill.py | 6 +++--- .../concept/ellens-alien-game/.meta/exemplar.py | 2 +- exercises/concept/ellens-alien-game/classes.py | 2 +- exercises/concept/locomotive-engineer/.docs/hints.md | 2 +- .../locomotive-engineer/.docs/instructions.md | 2 +- .../grains/.approaches/bit-shifting/content.md | 2 +- .../practice/grains/.approaches/introduction.md | 4 ++-- .../.approaches/mass-name-generation/content.md | 4 ++-- 14 files changed, 25 insertions(+), 25 deletions(-) diff --git a/concepts/function-arguments/about.md b/concepts/function-arguments/about.md index 09b01b1086..f4a9c2b3f2 100644 --- a/concepts/function-arguments/about.md +++ b/concepts/function-arguments/about.md @@ -182,7 +182,7 @@ For instance, `*` is used for multiplication, it is used for unpacking, and it i Since a tuple can be iterated, `args` can be passed to any other function which takes an iterable. Although `*args` is commonly juxtaposed with `**kwargs`, it doesn't have to be. -Following is an example of an arbitrary amount of values being passed to a function: +Following is an example of an arbitrary number of values being passed to a function: ```python @@ -196,7 +196,7 @@ Following is an example of an arbitrary amount of values being passed to a funct If `*args` follows one or more positional arguments, then `*args` will be what is left over after the positional arguments. -Following is an example of an arbitrary amount of values being passed to a function after a positional argument: +Following is an example of an arbitrary number of values being passed to a function after a positional argument: ```python @@ -210,7 +210,7 @@ Following is an example of an arbitrary amount of values being passed to a funct If one or more default arguments are defined after `*args` they are separate from the `*args` values. -To put it all together is an example of an arbitrary amount of values being passed to a function that also has a positional argument and a default argument: +To put it all together is an example of an arbitrary number of values being passed to a function that also has a positional argument and a default argument: ```python @@ -228,7 +228,7 @@ To put it all together is an example of an arbitrary amount of values being pass ``` -Note that when an argument is already in an iterable, such as a tuple or list, it needs to be unpacked before being passed to a function that takes an arbitrary amount of separate arguments. +Note that when an argument is already in an iterable, such as a tuple or list, it needs to be unpacked before being passed to a function that takes an arbitrary number of separate arguments. This is accomplished by using `*`, which is the [unpacking operator][unpacking operator]. `*` in this context _unpacks_ the container into its separate elements which are then transformed by `*args` into a tuple. @@ -257,7 +257,7 @@ The `**` transforms the group of named arguments into a [`dictionary`][dictionar Since a dictionary can be iterated, `kwargs` can be passed to any other function which takes an iterable. Although `**kwargs` is commonly juxtaposed with `*args`, it doesn't have to be. -Following is an example of an arbitrary amount of key-value pairs being passed to a function: +Following is an example of an arbitrary number of key-value pairs being passed to a function: ```python >>> def add(**kwargs): @@ -271,7 +271,7 @@ Note that the `dict.values()` method is called to iterate through the `kwargs` d When iterating a dictionary the default is to iterate the keys. -Following is an example of an arbitrary amount of key-value pairs being passed to a function that then iterates over `kwargs.keys()`: +Following is an example of an arbitrary number of key-value pairs being passed to a function that then iterates over `kwargs.keys()`: ```python >>> def concat(**kwargs): diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index d5a4f7271c..1155bcf7a5 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -177,7 +177,7 @@ This means calculations within `()` have the highest priority, followed by `**`, ## Precision & Representation -Integers in Python have [arbitrary precision][arbitrary-precision] -- the amount of digits is limited only by the available memory of the host system. +Integers in Python have [arbitrary precision][arbitrary-precision] -- the number of digits is limited only by the available memory of the host system. Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. diff --git a/concepts/recursion/about.md b/concepts/recursion/about.md index 1c66756caf..1cf2438826 100644 --- a/concepts/recursion/about.md +++ b/concepts/recursion/about.md @@ -6,7 +6,7 @@ Recursion can be viewed as another way to loop/iterate. And like looping, a Boolean expression or `True/False` test is used to know when to stop the recursive execution. _Unlike_ looping, recursion without termination in Python cannot not run infinitely. Values used in each function call are placed in their own frame on the Python interpreter stack. -If the total amount of function calls takes up more space than the stack has room for, it will result in an error. +If the total number of function calls takes up more space than the stack has room for, it will result in an error. ## Looping vs Recursive Implementation diff --git a/concepts/recursion/introduction.md b/concepts/recursion/introduction.md index aebfd6596b..fb7e197070 100644 --- a/concepts/recursion/introduction.md +++ b/concepts/recursion/introduction.md @@ -5,7 +5,7 @@ It can be viewed as another way to loop/iterate. Like looping, a Boolean expression or `True/False` test is used to know when to stop the recursive execution. _Unlike_ looping, recursion without termination in Python cannot not run infinitely. Values used in each function call are placed in their own frame on the Python interpreter stack. -If the total amount of function calls takes up more space than the stack has room for, it will result in an error. +If the total number of function calls takes up more space than the stack has room for, it will result in an error. ```python def print_increment(step, max_value): diff --git a/exercises/concept/electric-bill/.docs/introduction.md b/exercises/concept/electric-bill/.docs/introduction.md index 02f56f06c2..153b91cf3b 100644 --- a/exercises/concept/electric-bill/.docs/introduction.md +++ b/exercises/concept/electric-bill/.docs/introduction.md @@ -150,7 +150,7 @@ This means calculations within `()` have the highest priority, followed by `**`, ## Precision & Representation -Integers in Python have [arbitrary precision][arbitrary-precision] -- the amount of digits is limited only by the available memory of the host system. +Integers in Python have [arbitrary precision][arbitrary-precision] -- the number of digits is limited only by the available memory of the host system. Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system. Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers. diff --git a/exercises/concept/electric-bill/.meta/exemplar.py b/exercises/concept/electric-bill/.meta/exemplar.py index 2a00a39b9b..9da3d3def1 100644 --- a/exercises/concept/electric-bill/.meta/exemplar.py +++ b/exercises/concept/electric-bill/.meta/exemplar.py @@ -2,10 +2,10 @@ def get_extra_hours(hours): - """Return the amount of hours. + """Return the number of hours. - :param: hours: int - amount of hours. - :return: int - amount of "extra" hours. + :param: hours: int - number of hours. + :return: int - number of "extra" hours. """ return (hours + 3) % 24 diff --git a/exercises/concept/electric-bill/electric_bill.py b/exercises/concept/electric-bill/electric_bill.py index 52a92865a0..0a60a125e9 100644 --- a/exercises/concept/electric-bill/electric_bill.py +++ b/exercises/concept/electric-bill/electric_bill.py @@ -2,10 +2,10 @@ def get_extra_hours(hours): - """Return the amount of hours. + """Return the number of hours. - :param: hours: int - amount of hours. - :return: int - amount of "extra" hours. + :param: hours: int - number of hours. + :return: int - number of "extra" hours. """ pass diff --git a/exercises/concept/ellens-alien-game/.meta/exemplar.py b/exercises/concept/ellens-alien-game/.meta/exemplar.py index 789b05776c..ace31649c7 100644 --- a/exercises/concept/ellens-alien-game/.meta/exemplar.py +++ b/exercises/concept/ellens-alien-game/.meta/exemplar.py @@ -9,7 +9,7 @@ class Alien: (class)total_aliens_created: int x_coordinate: int - Position on the x-axis. y_coordinate: int - Position on the y-axis. - health: int - Amount of health points. + health: int - Number of health points. Methods ------- diff --git a/exercises/concept/ellens-alien-game/classes.py b/exercises/concept/ellens-alien-game/classes.py index 4e45b96ac7..a9a3d1edae 100644 --- a/exercises/concept/ellens-alien-game/classes.py +++ b/exercises/concept/ellens-alien-game/classes.py @@ -9,7 +9,7 @@ class Alien: (class)total_aliens_created: int x_coordinate: int - Position on the x-axis. y_coordinate: int - Position on the y-axis. - health: int - Amount of health points. + health: int - Number of health points. Methods ------- diff --git a/exercises/concept/locomotive-engineer/.docs/hints.md b/exercises/concept/locomotive-engineer/.docs/hints.md index 6bfae5f3e3..208188c0ad 100644 --- a/exercises/concept/locomotive-engineer/.docs/hints.md +++ b/exercises/concept/locomotive-engineer/.docs/hints.md @@ -16,7 +16,7 @@ ## 3. Add missing stops -- Using `**kwargs` as a function parameter will allow an arbitrary amount of keyword arguments to be passed. +- Using `**kwargs` as a function parameter will allow an arbitrary number of keyword arguments to be passed. - Using `**` as an argument will unpack a dictionary into keyword arguments. - You can put keyword arguments in a `{}` or `dict()`. - To get the values out of a dictionary, you can use the `.values()` method. diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md index 1d915c143a..99d0129eaa 100644 --- a/exercises/concept/locomotive-engineer/.docs/instructions.md +++ b/exercises/concept/locomotive-engineer/.docs/instructions.md @@ -48,7 +48,7 @@ The function should then `return` a `list` with the modifications. Now that all the wagon data is correct, Linus would like you to update the system's routing information. Along a transport route, a train might make stops at a few different stations to pick up and/or drop off cargo. -Each journey could have a different amount of these intermediary delivery points. +Each journey could have a different number of these intermediary delivery points. Your friend would like you to update the systems routing `dict` with any missing/additional delivery information. Implement a function `add_missing_stops()` that accepts a routing `dict` followed by a variable number of keyword arguments. diff --git a/exercises/practice/grains/.approaches/bit-shifting/content.md b/exercises/practice/grains/.approaches/bit-shifting/content.md index 409c4ffba0..9ca1035a51 100644 --- a/exercises/practice/grains/.approaches/bit-shifting/content.md +++ b/exercises/practice/grains/.approaches/bit-shifting/content.md @@ -36,7 +36,7 @@ For `total` we want all of the 64 bits set to `1` to get the sum of grains on al The easy way to do this is to set the 65th bit to `1` and then subtract `1`. To go back to our two-square example, if we can grow to three squares, then we can shift `1` two positions to the left for binary `100`, which is decimal `4`. -By subtracting `1` we get `3`, which is the total amount of grains on the two squares. +By subtracting `1` we get `3`, which is the total number of grains on the two squares. | Square | Binary Value | Decimal Value | | ------- | ------------ | ------------- | diff --git a/exercises/practice/grains/.approaches/introduction.md b/exercises/practice/grains/.approaches/introduction.md index c02d5b8ba6..44773aa358 100644 --- a/exercises/practice/grains/.approaches/introduction.md +++ b/exercises/practice/grains/.approaches/introduction.md @@ -7,8 +7,8 @@ Another approach is to use [`pow`][pow]. ## General guidance -The key to solving Grains is to focus on each square having double the amount of grains as the square before it. -This means that the amount of grains grows exponentially. +The key to solving Grains is to focus on each square having double the number of grains as the square before it. +This means that the number of grains grows exponentially. The first square has one grain, which is `2` to the power of `0`. The second square has two grains, which is `2` to the power of `1`. The third square has four grains, which is `2` to the power of `2`. diff --git a/exercises/practice/robot-name/.approaches/mass-name-generation/content.md b/exercises/practice/robot-name/.approaches/mass-name-generation/content.md index 6e5fad83e9..392a34ca19 100644 --- a/exercises/practice/robot-name/.approaches/mass-name-generation/content.md +++ b/exercises/practice/robot-name/.approaches/mass-name-generation/content.md @@ -42,8 +42,8 @@ However, this has a huge startup memory and time cost, as 676,000 strings have t For an approximate calculation, 676,000 strings * 5 characters / string * 1 byte / character gives 3380000 bytes or 3.38 MB of RAM - and that's just the memory aspect of it. Sounds small, but it's relatively very expensive at the beginning. -Thus, this approach is inefficient in cases where only a small amount of names are needed _and_ the time to set/reset the robot isn't crucial. +Thus, this approach is inefficient in cases where only a small number of names are needed _and_ the time to set/reset the robot isn't crucial. [random-seed]: https://docs.python.org/3/library/random.html#random.seed [iter-and-next]: https://www.programiz.com/python-programming/methods/built-in/iter -[itertools-product]: https://www.hackerrank.com/challenges/itertools-product/problem \ No newline at end of file +[itertools-product]: https://www.hackerrank.com/challenges/itertools-product/problem From 939b0e3a3bcc86c2a63900717ccecc296272de3e Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sun, 29 Oct 2023 00:30:52 +0200 Subject: [PATCH 533/826] Fix grammar in currency-exchange hint (#3529) This is a tiny thing, and it probably wouldn't confuse people who are actually doing the exercise. I came across it because I was doing a full audit of instances where we say 'amount of ...' since there were several instances where it should have been 'number of ...'. At first I thought this was supposed to be 'number of changes', but then in the context of the exercise it turns out that it was a different problem entirely :) --- exercises/concept/currency-exchange/.docs/hints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/currency-exchange/.docs/hints.md b/exercises/concept/currency-exchange/.docs/hints.md index fb27ff7761..896472488e 100644 --- a/exercises/concept/currency-exchange/.docs/hints.md +++ b/exercises/concept/currency-exchange/.docs/hints.md @@ -10,7 +10,7 @@ ## 2. Calculate currency left after an exchange -- You can use the [subtraction operator][subtraction-operator] to get the amount of changes. +- You can use the [subtraction operator][subtraction-operator] to get the amount of change. ## 3. Calculate value of bills From a986374b7ba7d1f3412851b38297c883f348e9cd Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 31 Oct 2023 11:55:50 +0100 Subject: [PATCH 534/826] Launch plane tickets concept exercise in beta (#3530) * Tweak plane tickets concept exercise I did a small pass through to tweak it a bit for flow and readability. This exercise is ready to be launched. * Turn on plane tickets exercise * (BG) Rather More Edits than Anticipated: This all started with me re-editing the docstrings to comply with https://peps.python.org/pep-0257/, but then it snowballed. Turns out that typing.Generator (https://docs.python.org/3/library/typing.html#typing.Generator) has been deprecated as an alias, so the tests needed some love...and that snowballed into reviewing the other docs. Also renamed the stub and test files to be in line with what we did with other concept exercises. --------- Co-authored-by: BethanyG --- concepts/generators/about.md | 12 ++ concepts/generators/introduction.md | 10 +- concepts/generators/links.json | 6 +- config.json | 2 +- .../concept/plane-tickets/.docs/hints.md | 10 +- .../plane-tickets/.docs/instructions.md | 26 ++-- .../plane-tickets/.docs/introduction.md | 66 +++++---- .../concept/plane-tickets/.meta/config.json | 10 +- .../concept/plane-tickets/.meta/exemplar.py | 44 +++--- .../{plane_tickets.py => generators.py} | 33 +++-- .../concept/plane-tickets/generators_test.py | 137 ++++++++++++++++++ .../plane-tickets/plane_tickets_test.py | 88 ----------- 12 files changed, 272 insertions(+), 172 deletions(-) rename exercises/concept/plane-tickets/{plane_tickets.py => generators.py} (55%) create mode 100644 exercises/concept/plane-tickets/generators_test.py delete mode 100644 exercises/concept/plane-tickets/plane_tickets_test.py diff --git a/concepts/generators/about.md b/concepts/generators/about.md index 5995b27c0e..59b5035d6b 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -1,5 +1,13 @@ # About +A `generator` is a function or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator]. +`Generator-iterators` are [lazy][lazy iterator]: they do not store their `values` in memory, but _generate_ their values when needed. + +A generator function looks like any other function, but contains one or more [yield expressions][yield expression]. +Each `yield` will suspend code execution, saving the current execution state (_including all local variables and try-statements_). +When the generator resumes, it picks up state from the suspension - unlike regular functions which reset with every call. + + ## Constructing a generator Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. @@ -131,5 +139,9 @@ Generators are also very helpful when a process or calculation is _complex_, _ex Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. + +[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator [iterables]: https://wiki.python.org/moin/Iterator +[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator +[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation [yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index 82a686d1e0..ad1175ca0b 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -1,7 +1,13 @@ # Introduction -A generator in Python is a _callable function_ that returns a [lazy iterator][lazy iterator]. +A generator in Python is a _callable function_ or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator]. +`Generator-iterators` are [lazy][lazy iterator]: they do not store their `values` in memory, but _generate_ their values when needed. -_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. +A generator function looks like any other function, but contains one or more [yield expressions][yield expression]. +Each `yield` will suspend code execution, saving the current execution state (_including all local variables and try-statements_). +When the generator function resumes, it picks up state from the suspension - unlike regular functions which reset with every call. [lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation +[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator +[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions +[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator diff --git a/concepts/generators/links.json b/concepts/generators/links.json index 972bbe7ae9..134a723c69 100644 --- a/concepts/generators/links.json +++ b/concepts/generators/links.json @@ -1,12 +1,16 @@ [ { - "url": "https://docs.python.org/3.10/reference/expressions.html#yield-expressions", + "url": "https://docs.python.org/3.11/reference/expressions.html#yield-expressions", "description": "Official Python 3.10 docs for the yield expression." }, { "url": "https://en.wikipedia.org/wiki/Lazy_evaluation", "description": "Wikipedia page about lazy evaluation" }, + { + "url": "https://www.pythonmorsels.com/iterators/", + "description": "Python Morsels: Iterators & Generators" + }, { "url": "https://realpython.com/introduction-to-python-generators/", "description": "Real python, introduction to generators and yield" diff --git a/config.json b/config.json index 37b140d2f6..e9d353e99b 100644 --- a/config.json +++ b/config.json @@ -193,7 +193,7 @@ "uuid": "3ba3fc89-3e1b-48a5-aff0-5aeaba8c8810", "concepts": ["generators"], "prerequisites": ["conditionals", "dicts", "lists", "loops", "classes"], - "status": "wip" + "status": "beta" }, { "slug": "log-levels", diff --git a/exercises/concept/plane-tickets/.docs/hints.md b/exercises/concept/plane-tickets/.docs/hints.md index 11508ee383..2c2203518f 100644 --- a/exercises/concept/plane-tickets/.docs/hints.md +++ b/exercises/concept/plane-tickets/.docs/hints.md @@ -4,19 +4,19 @@ - The returned value should be of _type_ `generator`. - You can have a sequence of letters from `A` to `D` and cycle through them. -- And use `yield` to return the next letter. +- Use `yield` to return the next letter. -## 2. Generate an amount of seats +## 2. Generate seats - The returned value should be of _type_ `generator`. - Row `13` should be skipped, so go from `12` to `14`. - Keep in mind that the returned values should be ordered from low to high. `1A, 1B, 2A, ...` -- It might be good to reuse or call other functions you have already completed here. +- Here it might be good to reuse or call functions you have already defined. ## 3. Assign seats to passengers -- Make sure your seat numbers do not have any space in them. -- It might be good to reuse or call other functions you have already completed here. +- Make sure your seat numbers do not have any spaces in them. +- Here it might be good to reuse or call functions you have already defined. ## 4. Ticket codes diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index b22a84f221..edd92680b1 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -1,10 +1,10 @@ # Instructions -Conda airlines is the programming-world's biggest airline, with over 10.000 flights a day! +Conda Airlines is the programming-world's biggest airline, with over 10,000 flights a day! -They are currently assigning all seats to passengers by hand, this will need to be automated. +They are currently assigning all seats to passengers by hand; this will need to be automated. -They have asked _you_ to create software to automate the assigning of seats to passengers. +They have asked _you_ to create software to automate passenger seat assignments. They require your software to be memory efficient and performant. ## 1. Generate seat letters @@ -12,11 +12,11 @@ They require your software to be memory efficient and performant. Conda wants to generate seat letters for their airplanes. An airplane is made of rows of seats. Each row has _4 seats_. -The rows seats has the same naming: `A`, `B`, `C`, `D`. -Meaning the first seat in the row is `A`, the second seat in the row is `B`, and so on. -After reaching `D` it should start again with `A`. +The seats in each row are always named `A`, `B`, `C`, and `D`. +The first seat in the row is `A`, the second seat in the row is `B`, and so on. +After reaching `D`, it should start again with `A`. -Implement a function `generate_seat_letters()` that accepts an `int` that holds how many seat letters to be generated. +Implement a function `generate_seat_letters()` that accepts an `int` that holds how many seat letters to be generated. The function should then return an _iterable_ of seat letters. ```python @@ -27,9 +27,9 @@ The function should then return an _iterable_ of seat letters. "B" ``` -## 2. Generate an amount of seats +## 2. Generate seats -Conda wants a system that can generate an amount of seats for their airplanes. +Conda wants a system that can generate a given number of seats for their airplanes. Each airplane has _4 seats_ in each row. The rows are defined using numbers, starting from `1` and going up. The seats should be ordered, like: `1A`, `1B`, `1C`, `1D`, `2A`, `2B`, `2C`, `2D`, `3A`, `3B`, `3C`, `3D`, ... @@ -45,7 +45,7 @@ Here is an example: Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13. -Implement a function `generate_seats()` that accepts an `int` that holds how many seats to be generated. +Implement a function `generate_seats()` that accepts an `int` that holds how many seats to be generated. The function should then return an _iterable_ of seats given. ```python @@ -60,7 +60,7 @@ The function should then return an _iterable_ of seats given. Now that you have a function that generates seats, you can use it to assign seats to passengers. -Implement a function `assign_seats()` that accepts a `list` of passenger names. +Implement a function `assign_seats()` that accepts a `list` of passenger names. The function should then return a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. ```python @@ -74,13 +74,13 @@ The function should then return a _dictionary_ of `passenger` as _key_, and `sea Conda Airlines would like to have a unique code for each ticket. Since they are a big airline, they have a lot of flights. -Meaning that there are multiple flights with the same seat number. +This means that there are multiple flights with the same seat number. They want you to create a system that creates a unique ticket that is _12_ characters long string code for identification. This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`. -Implement a function `generate_codes()` that accepts a `list` of `seat_numbers` and a `string` with the flight number. +Implement a function `generate_codes(, )` that accepts a `list` of `seat_numbers` and a `string` with the flight number. The function should then return a `generator` that yields a `ticket_number`. ```python diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 88917fc805..d17f90c812 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -1,10 +1,18 @@ -# About +# Generators + +A `generator` is a function or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator]. +`Generator-iterators` are [lazy][lazy iterator]: they do not store their `values` in memory, but _generate_ their values when needed. + +A generator function looks like any other function, but contains one or more [yield expressions][yield expression]. +Each `yield` will suspend code execution, saving the current execution state (_including all local variables and try-statements_). +When the generator resumes, it picks up state from the suspension - unlike regular functions which reset with every call. + ## Constructing a generator Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. -An example is a function that returns the _squares_ from a given list of numbers. +An example is a function that returns the _squares_ from a given list of numbers. As currently written, all input must be processed before any values can be returned: ```python @@ -23,13 +31,14 @@ You can convert that function into a generator like this: ... yield number ** 2 ``` -The rationale behind this is that you use a generator when you do not need all the values _at once_. - +The rationale behind this is that you use a generator when you do not need to produce all the values _at once_. This saves memory and processing power, since only the value you are _currently working on_ is calculated. + ## Using a generator -Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. +Generators may be used in place of most `iterables` in Python. +This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. To use the `squares_generator()` generator: @@ -45,8 +54,8 @@ To use the `squares_generator()` generator: 16 ``` -Values within a generator can also be produced/accessed via the `next()` function. -`next()` calls the `__next__()` method of a generator object, "advancing" or evaluating the generator code up to its `yield` expression, which then "yields" or returns the value. +Values within a `generator` can also be produced/accessed via the `next()` function. +`next()` calls the `__next__()` method of a generator-iterator object, "advancing" or evaluating the code up to its `yield` expression, which then "yields" or returns a value: ```python >>> squared_numbers = squares_generator([1, 2]) @@ -57,7 +66,7 @@ Values within a generator can also be produced/accessed via the `next()` functio 4 ``` -When a `generator` is fully consumed and has no more values to return, it throws a `StopIteration` error. +When a `generator-iterator` is fully consumed and has no more values to return, it throws a `StopIteration` error. ```python >>> next(squared_numbers) @@ -66,34 +75,35 @@ Traceback (most recent call last): StopIteration ``` -### Difference between iterables and generators -Generators are a special sub-set of _iterators_. -`Iterators` are the mechanism/protocol that enables looping over _iterables_. -Generators and the iterators returned by common Python [`iterables`][iterables] act very similarly, but there are some important differences to note: - -- Generators are _one-way_; there is no "backing up" to a previous value. - -- Iterating over generators consume the returned values; no resetting. +~~~~exercism/note -- Generators (_being lazily evaluated_) are not sortable and can not be reversed. +Generator-iterators are a special sub-set of [iterators][iterator]. +`Iterators` are the mechanism/protocol that enables looping over _iterables_. +Generator-iterators and the iterators returned by common Python [`iterables`][iterables] act very similarly, but there are some important differences to note: -- Generators do _not_ have `indexes`, so you can't reference a previous or future value using addition or subtraction. +- They are _[lazily evaluated][lazy evaluation]_; iteration is _one-way_ and there is no "backing up" to a previous value. +- They are _consumed_ by iterating over the returned values; there is no resetting or saving in memory. +- They are not sortable and cannot be reversed. +- They are not sequence types, and _do not_ have `indexes`. + You cannot reference a previous or future value using addition or subtraction and you cannot use bracket (`[]`) notation or slicing. +- They cannot be used with the `len()` function, as they have no length. +- They can be _finite_ or _infinite_ - be careful when collecting all values from an _infinite_ `generator-iterator`! -- Generators cannot be used with the `len()` function. +[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator +[iterables]: https://wiki.python.org/moin/Iterator +[lazy evaluation]: https://en.wikipedia.org/wiki/Lazy_evaluation +~~~~ -- Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator. ## The yield expression The [yield expression][yield expression] is very similar to the `return` expression. - _Unlike_ the `return` expression, `yield` gives up values to the caller at a _specific point_, suspending evaluation/return of any additional values until they are requested. - When `yield` is evaluated, it pauses the execution of the enclosing function and returns any values of the function _at that point in time_. - The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again. + ~~~~exercism/note Using `yield` expressions is prohibited outside of functions. ~~~~ @@ -112,11 +122,12 @@ Using `yield` expressions is prohibited outside of functions. 1 ``` -## Why generators? + +## Why Create a Generator? Generators are useful in a lot of applications. -When working with a large collection, you might not want to put all of its values into `memory`. +When working with a potentially large collection of values, you might not want to put all of them into memory. A generator can be used to work on larger data piece-by-piece, saving memory and improving performance. Generators are also very helpful when a process or calculation is _complex_, _expensive_, or _infinite_: @@ -131,5 +142,10 @@ Generators are also very helpful when a process or calculation is _complex_, _ex Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. + +[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator [iterables]: https://wiki.python.org/moin/Iterator +[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator +[lazy evaluation]: https://en.wikipedia.org/wiki/Lazy_evaluation +[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation [yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions diff --git a/exercises/concept/plane-tickets/.meta/config.json b/exercises/concept/plane-tickets/.meta/config.json index 2698188896..1222834884 100644 --- a/exercises/concept/plane-tickets/.meta/config.json +++ b/exercises/concept/plane-tickets/.meta/config.json @@ -3,19 +3,21 @@ "J08K" ], "contributors": [ - "BethanyG" + "BethanyG", + "kytrinyx", + "meatball133" ], "files": { "solution": [ - "plane_tickets.py" + "generators.py" ], "test": [ - "plane_tickets_test.py" + "generators_test.py" ], "exemplar": [ ".meta/exemplar.py" ] }, "icon": "new-passport", - "blurb": "Learn about generators by assigning seats to passengers." + "blurb": "Learn about generators by assigning seats to passengers on Anaconda Airlines." } diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 9a3d702407..8261795c66 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -5,10 +5,10 @@ SEATS_IN_ROW = ['A', 'B', 'C', 'D'] -def generate_seat_letters(amount): - """Generate a series of seat letters for airline boarding. +def generate_seat_letters(number): + """Generate a series of letters for airline seats. - :param amount: int - amount of seat letters to be generated. + :param number: int - total number of seat letters to be generated. :return: generator - generator that yields seat letters. Seat letters are generated from A to D. @@ -18,45 +18,51 @@ def generate_seat_letters(amount): """ - for seat in range(amount): + for seat in range(number): yield SEATS_IN_ROW[seat % 4] -def generate_seats(amount): - """Generate a series of seat numbers for airline boarding. +def generate_seats(number): + """Generate a series of identifiers for airline seats. - :param amount: int - Amount of seats to be generated. + :param number: int - total number of seats to be generated. :return: generator - generator that yields seat numbers. - There should be no row 13 + A seat number consists of the row number and the seat letter. - Seat numbers are generated with each row having 4 seats. - These should be sorted from low to high. + There is no row 13. + Each row has 4 seats. + + Seats should be sorted from low to high. Example: 3C, 3D, 4A, 4B """ - amount = amount + 4 if amount >= 13 else amount - letters = generate_seat_letters(amount) - for seat in range(amount): + number = number + 4 if number >= 13 else number + letters = generate_seat_letters(number) + for seat in range(number): row_number = math.ceil((seat+1) / 4) if row_number != 13: yield f'{str(row_number)}{next(letters)}' + # return (f'{str(row_number)}{next(letters)}' for seat in range(number) + # if (row_number := math.ceil((seat+1) / 4)) and row_number != 13) + + def assign_seats(passengers): """Assign seats to passengers. - :param passengers: list[str] - A list of strings containing names of passengers. + :param passengers: list[str] - a list of strings containing names of passengers. :return: dict - with the names of the passengers as keys and seat numbers as values. - Example output: {"Foo": "1A", "Bar": "1B"} + Example output: {"Adele": "1A", "Björk": "1B"} """ - amount = len(passengers) + number = len(passengers) output = {} - for passenger, seat_number in zip(passengers, generate_seats(amount)): + for passenger, seat_number in zip(passengers, generate_seats(number)): output[passenger] = seat_number return output @@ -64,8 +70,8 @@ def generate_codes(seat_numbers, flight_id): """Generate codes for a ticket. :param seat_numbers: list[str] - list of seat numbers. - :param flight_id: str - string containing the flight identification. - :return: generator - generator that yields 12 character long strings. + :param flight_id: str - string containing the flight identifier. + :return: generator - generator that yields 12 character long ticket codes. """ diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/generators.py similarity index 55% rename from exercises/concept/plane-tickets/plane_tickets.py rename to exercises/concept/plane-tickets/generators.py index 4399791b22..2f88c0619a 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/generators.py @@ -1,9 +1,10 @@ """Functions to automate Conda airlines ticketing system.""" -def generate_seat_letters(amount): - """ Generate a series of seat letters for airline boarding. - :param amount: int - amount of seat letters to be generated. +def generate_seat_letters(number): + """Generate a series of letters for airline seats. + + :param number: int - total number of seat letters to be generated. :return: generator - generator that yields seat letters. Seat letters are generated from A to D. @@ -16,16 +17,18 @@ def generate_seat_letters(amount): pass -def generate_seats(amount): - """ Generate a series of seat numbers for airline boarding. +def generate_seats(number): + """Generate a series of identifiers for airline seats. - :param amount: int - Amount of seats to be generated. + :param number: int - total number of seats to be generated. :return: generator - generator that yields seat numbers. - There should be no row 13 + A seat number consists of the row number and the seat letter. + + There is no row 13. + Each row has 4 seats. - Seat numbers are generated with each row having 4 seats. - These should be sorted from low to high. + Seats should be sorted from low to high. Example: 3C, 3D, 4A, 4B @@ -34,12 +37,12 @@ def generate_seats(amount): pass def assign_seats(passengers): - """ Assign seats to passengers. + """Assign seats to passengers. - :param passengers: list[str] - A list of strings containing names of passengers. + :param passengers: list[str] - a list of strings containing names of passengers. :return: dict - with the names of the passengers as keys and seat numbers as values. - Example output: {"Foo": "1A", "Bar": "1B"} + Example output: {"Adele": "1A", "Björk": "1B"} """ @@ -49,7 +52,9 @@ def generate_codes(seat_numbers, flight_id): """Generate codes for a ticket. :param seat_numbers: list[str] - list of seat numbers. - :param flight_id: str - string containing the flight identification. - :return: generator - generator that yields 12 character long strings. + :param flight_id: str - string containing the flight identifier. + :return: generator - generator that yields 12 character long ticket codes. """ + + pass diff --git a/exercises/concept/plane-tickets/generators_test.py b/exercises/concept/plane-tickets/generators_test.py new file mode 100644 index 0000000000..0cab475ea1 --- /dev/null +++ b/exercises/concept/plane-tickets/generators_test.py @@ -0,0 +1,137 @@ +import inspect +import unittest +import pytest + +from generators import ( + generate_seat_letters, + generate_seats, + assign_seats, + generate_codes +) + +class PlaneTicketsTest(unittest.TestCase): + @pytest.mark.task(taskno=1) + def test_task1_returns_generator(self): + """Test if generate_seat_letters() returns a generator type.""" + + number = 5 + error_message = (f'Called generate_seat_letters({number}). ' + f'The function returned a {type(generate_seat_letters(number))} type, ' + f"but the tests expected the function to return a type.") + + self.assertTrue(inspect.isgenerator(generate_seat_letters(number)), msg=error_message) + + @pytest.mark.task(taskno=1) + def test_generate_seat_letters(self): + test_data = [1, 2, 3, 4, 5] + result_data = [["A"], + ["A", "B"], + ["A", "B", "C"], + ["A", "B", "C", "D"], + ["A", "B", "C", "D", "A"]] + + for variant, (number, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f"variation #{variant}", number=number, expected=expected): + actual_result = list(generate_seat_letters(number)) + error_message = (f'Called generate_seat_letters({number}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} when generating {number} seat(s).') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_task2_returns_generator(self): + """Test if generate_seats() returns a generator type.""" + + number = 7 + error_message = (f'Called generate_seat_letters({number}). ' + f'The function returned a {type(generate_seats(number))} type, ' + f"but the tests expected the function to return a type.") + + self.assertTrue(inspect.isgenerator(generate_seats(number)), msg=error_message) + + @pytest.mark.task(taskno=2) + def test_generate_seats(self): + test_data = [1, 2, 3, 4, 5] + result_data = [["1A"], + ["1A", "1B"], + ["1A", "1B", "1C"], + ["1A", "1B", "1C", "1D"], + ["1A", "1B", "1C", "1D", "2A"]] + + for variant, (number, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f"variation #{variant}", number=number, expected=expected): + actual_result = list(generate_seats(number)) + error_message = (f'Called generate_seats({number}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} when generating {number} seat(s).') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_generate_seats_skips_row_13(self): + test_data = [14 * 4] + result_data = [["1A", "1B", "1C", "1D", "2A", "2B", "2C", "2D", + "3A", "3B", "3C", "3D", "4A", "4B", "4C", "4D", + "5A", "5B", "5C", "5D", "6A", "6B", "6C", "6D", + "7A", "7B", "7C", "7D", "8A", "8B", "8C", "8D", + "9A", "9B", "9C", "9D", "10A", "10B", "10C", "10D", + "11A", "11B", "11C", "11D", "12A", "12B", "12C", "12D", + "14A", "14B", "14C", "14D", "15A", "15B", "15C", "15D"]] + + for variant, (number, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f"variation #{variant}", number=number, expected=expected): + actual_result = list(generate_seats(number)) + error_message = (f'Called generate_seats({number}). ' + f'The function returned {actual_result}, but the tests ' + f'expected: {expected}, when generating {number} seat(s).') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_assign_seats(self): + test_data = [["Passenger1", "Passenger2", "Passenger3", "Passenger4", "Passenger5"], + ["TicketNo=5644", "TicketNo=2273", "TicketNo=493", "TicketNo=5411", "TicketNo=824"]] + result_data = [{"Passenger1": "1A", "Passenger2": "1B", + "Passenger3": "1C", "Passenger4": "1D", "Passenger5": "2A"}, + {"TicketNo=5644": "1A", "TicketNo=2273": "1B", + "TicketNo=493": "1C", "TicketNo=5411": "1D", "TicketNo=824": "2A"}] + + for variant, (passengers, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f"variation #{variant}", passengers=passengers, expected=expected): + actual_result = assign_seats(passengers) + error_message = (f'Called assign_seats({passengers}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected}, when assigning seats.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_task4_returns_generator(self): + """Test if generate_codes() returns a generator type.""" + + seat_numbers, flight_id = "11B", "HA80085" + error_message = (f'Called generate_codes({seat_numbers}, {flight_id}). ' + f'The function returned a {type(generate_codes(seat_numbers, flight_id))} type, ' + f"but the tests expected the function to return a type.") + + self.assertTrue(inspect.isgenerator(generate_codes(seat_numbers, flight_id)), msg=error_message) + + + @pytest.mark.task(taskno=4) + def test_generate_codes(self): + test_data = [(["12A", "38B", "69C", "102B"],"KL1022"), + (["22C", "88B", "33A", "44B"], "DL1002")] + result_data = [['12AKL1022000', '38BKL1022000', '69CKL1022000', '102BKL102200'], + ['22CDL1002000', '88BDL1002000', '33ADL1002000', '44BDL1002000']] + + for variant, ((seat_numbers, flight_id), expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f"variation #{variant}", seat_numbbers=seat_numbers, + flight_id=flight_id, expected=expected): + + actual_result = list(generate_codes(seat_numbers, flight_id)) + error_message = (f'Called generate_codes({seat_numbers}, {flight_id}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} when generating ticket numbers.') + + self.assertEqual(list(generate_codes(seat_numbers, flight_id)), expected, msg=error_message) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py deleted file mode 100644 index 1372e5cd2d..0000000000 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ /dev/null @@ -1,88 +0,0 @@ -from typing import Generator -import unittest -import pytest - -from plane_tickets import ( - generate_seat_letters, - generate_seats, - assign_seats, - generate_codes -) - -class PlaneTicketsTest(unittest.TestCase): - - @pytest.mark.task(taskno=1) - def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. - input_var = 5 - output_type = Generator - error_message = f"Expected: {str(output_type)} type, but got a different type." - self.assertIsInstance(generate_seat_letters(input_var), output_type, msg=error_message) - - @pytest.mark.task(taskno=1) - def test_task1_output(self): - input_vars = [1, 2, 3, 4, 5] - output = [["A"], ["A", "B"], ["A", "B", "C"], ["A", "B", "C", "D"], ["A", "B", "C", "D", "A"]] - for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): - error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)." - with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(list(generate_seat_letters(input_var)), output, msg=error_message) - - @pytest.mark.task(taskno=2) - def test_task2_is_generator(self): # * Tests if [Task 2] actually returns a generator. - input_var = 5 - output_type = Generator - error_message = f"Expected: {str(output_type)} type, but got a different type." - self.assertIsInstance(generate_seats(input_var), output_type, msg=error_message) - - @pytest.mark.task(taskno=2) - def test_task2_output(self): - input_vars = [1, 2, 3, 4, 5] - output = [["1A"], ["1A", "1B"], ["1A", "1B", "1C"], ["1A", "1B", "1C", "1D"], ["1A", "1B", "1C", "1D", "2A"]] - for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): - error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)." - with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(list(generate_seats(input_var)), output, msg=error_message) - - @pytest.mark.task(taskno=2) - def test_task3_skips_row_13(self): - input_vars = [14 * 4] - output = [["1A", "1B", "1C", "1D", "2A", "2B", "2C", "2D", - "3A", "3B", "3C", "3D", "4A", "4B", "4C", "4D", - "5A", "5B", "5C", "5D", "6A", "6B", "6C", "6D", - "7A", "7B", "7C", "7D", "8A", "8B", "8C", "8D", - "9A", "9B", "9C", "9D", "10A", "10B", "10C", "10D", - "11A", "11B", "11C", "11D", "12A", "12B", "12C", "12D", - "14A", "14B", "14C", "14D", "15A", "15B", "15C", "15D"]] - for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): - error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)." - with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(list(generate_seats(input_var)), output, msg=error_message) - - @pytest.mark.task(taskno=3) - def test_task3(self): - input_vars = [["Passenger1", "Passenger2", "Passenger3", "Passenger4", "Passenger5"], - ["TicketNo=5644", "TicketNo=2273", "TicketNo=493", "TicketNo=5411", "TicketNo=824"]] - output = [{"Passenger1": "1A", "Passenger2": "1B", "Passenger3": "1C", "Passenger4": "1D", "Passenger5": "2A"}, - {"TicketNo=5644": "1A", "TicketNo=2273": "1B", "TicketNo=493": "1C", "TicketNo=5411": "1D", "TicketNo=824": "2A"}] - for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): - error_message = f"Expected: {output}, but something went wrong while assigning seats to passengers {input_var}." - with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(assign_seats(input_var), output, msg=error_message) - - @pytest.mark.task(taskno=4) - def test_task4_is_generator(self): - input_var = ("11B", "HA80085") - output_type = Generator - error_message = f"Expected: {str(output_type)} type, but got a different type." - self.assertIsInstance(generate_codes(input_var[0], input_var[1]), output_type, msg=error_message) - - @pytest.mark.task(taskno=4) - def test_task4(self): - input_vars = [(["12A", "38B", "69C", "102B"],"KL1022"), - (["22C", "88B", "33A", "44B"], "DL1002")] - output = [['12AKL1022000', '38BKL1022000', '69CKL1022000', '102BKL102200'], - ['22CDL1002000', '88BDL1002000', '33ADL1002000', '44BDL1002000']] - for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): - error_message = f"Expected: {input_var}, but something went wrong while generating ticket numbers." - with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(list(generate_codes(input_var[0], input_var[1])), output, msg=error_message) \ No newline at end of file From dc64b8f02ea89eb16e138d6996e81a962bfca7ec Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 31 Oct 2023 04:22:53 -0700 Subject: [PATCH 535/826] [Generator Concept]: Updated config.json (#3531) * Update config.json for the Generator Concept Added Bethany, Kytrinyx, and Meatball133 as contributors and updated the concept blurb. * Update concepts/generators/.meta/config.json * Update concepts/generators/.meta/config.json * Update concepts/generators/.meta/config.json --- concepts/generators/.meta/config.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json index 6c29169d3f..3322727ef7 100644 --- a/concepts/generators/.meta/config.json +++ b/concepts/generators/.meta/config.json @@ -1,5 +1,9 @@ { - "blurb": "Generator are functions that returns a lazy iterator, lazy iterator is an iterator like: list, tuple, etc. But doesn't need store its content in memory", + "blurb": "Generators are functions or expressions that return a special type of iterator called a 'generator-iterator'. Generator-iterators are lazy: they do not store their values in memory but generate them when needed.", "authors": ["J08K"], - "contributors": [] + "contributors": [ + "BethanyG", + "kytrinyx", + "meatball133" + ] } From 2effbddf09255449c07c0b7bd7b5b11bdf0f8424 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 31 Oct 2023 17:43:23 -0700 Subject: [PATCH 536/826] Update generators_test.py (#3533) Bad typo. [forum post](https://forum.exercism.org/t/wrong-error-message-in-plane-tickets-python-track-exercise/7984) [no important files changed] --- exercises/concept/plane-tickets/generators_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/generators_test.py b/exercises/concept/plane-tickets/generators_test.py index 0cab475ea1..12a3532aa5 100644 --- a/exercises/concept/plane-tickets/generators_test.py +++ b/exercises/concept/plane-tickets/generators_test.py @@ -44,7 +44,7 @@ def test_task2_returns_generator(self): """Test if generate_seats() returns a generator type.""" number = 7 - error_message = (f'Called generate_seat_letters({number}). ' + error_message = (f'Called generate_seats({number}). ' f'The function returned a {type(generate_seats(number))} type, ' f"but the tests expected the function to return a type.") From 6f87ae7d6cce1024b9d4b893f31213521e0e3aca Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 1 Nov 2023 14:14:22 +0100 Subject: [PATCH 537/826] Update the metadata for the `pop-count` exercise (#3534) --- config.json | 2 +- exercises/practice/pop-count/.meta/config.json | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config.json b/config.json index e9d353e99b..98891388c2 100644 --- a/config.json +++ b/config.json @@ -970,7 +970,7 @@ }, { "slug": "pop-count", - "name": "Pop Count", + "name": "Eliud's Eggs", "uuid": "356e2d29-7efc-4fa3-bec7-8b61c3e967da", "practices": ["loops"], "prerequisites": [ diff --git a/exercises/practice/pop-count/.meta/config.json b/exercises/practice/pop-count/.meta/config.json index baf822a16a..36b1259ace 100644 --- a/exercises/practice/pop-count/.meta/config.json +++ b/exercises/practice/pop-count/.meta/config.json @@ -1,5 +1,7 @@ { - "authors": ["vaeng"], + "authors": [ + "vaeng" + ], "files": { "solution": [ "pop_count.py" @@ -11,7 +13,7 @@ ".meta/example.py" ] }, - "blurb": "Count the 1 bits in a number", + "blurb": "Help Eluid count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", "source": "Christian Willner, Eric Willigers", "source_url": "https://forum.exercism.org/t/new-exercise-suggestion-pop-count/7632/5" } From 0d2c09b5687121f43d2f9dbee37a60ffb3ed919a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 21:59:36 -0800 Subject: [PATCH 538/826] Updated test error messages and fixed a couple of tests. (#3546) --- .../dict_methods_test.py | 111 ++++++++++++------ 1 file changed, 76 insertions(+), 35 deletions(-) diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 2f5828d615..2c9e36948c 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -1,5 +1,6 @@ import unittest import pytest +from collections import OrderedDict from dict_methods import (add_item, read_notes, update_recipes, @@ -22,10 +23,14 @@ def test_add_item(self): {'Orange': 1, 'Raspberry': 3, 'Blueberries': 11}, {'Broccoli': 2, 'Banana': 3, 'Kiwi': 3, 'Melon': 1, 'Apple': 1}] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different shopping cart.' - self.assertEqual(add_item(input_data[0], input_data[1]), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + actual_result = add_item(input_data[0], input_data[1]) + error_msg= (f'Called add_item({input_data[0]}, {input_data[1]}). ' + f'The function returned {actual_result}, but the tests ' + f'expected: {expected} once the item was added.') + + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=2) @@ -36,10 +41,14 @@ def test_read_notes(self): output_data = [{'Apple': 1, 'Banana': 1}, {'Orange': 1, 'Raspberry': 1, 'Blueberries': 1}, {'Broccoli': 1, 'Kiwi': 1, 'Melon': 1, 'Apple': 1, 'Banana': 1}] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different shopping cart.' - self.assertEqual(read_notes(input_data), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + actual_result = read_notes(input_data) + error_msg = (f'Called read_notes({input_data}). ' + f'The function returned {actual_result}, but the tests ' + f'expected: {expected} once the notes were read.') + + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=3) def test_update_recipes(self): @@ -72,10 +81,14 @@ def test_update_recipes(self): 'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}} ] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different ideas instead.' - self.assertEqual(update_recipes(input_data[0], input_data[1]), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + actual_result = update_recipes(input_data[0], input_data[1]) + error_msg = (f'Called update_recipes({input_data[0]}, {input_data[1]}). ' + f'The function returned {actual_result}, but the tests ' + f'expected: {expected} once the recipes were updated.') + + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=4) def test_sort_entries(self): @@ -88,41 +101,65 @@ def test_sort_entries(self): output_data = [ {'Apple': 2, 'Banana': 4, 'Orange': 1, 'Pear': 12}, - {'Avocado': 2, 'Apple': 3, 'Banana': 1, 'Orange': 5}, - {'Apple': 1, 'Orange': 3, 'Banana': 2}, + {'Apple': 3, 'Avocado': 2, 'Banana': 1, 'Orange': 5}, + {'Apple': 1, 'Banana': 2, 'Orange': 3}, {'Apple' : 2, 'Blueberries': 5, 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4, 'Raspberry': 2} ] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different sorted list instead.' - self.assertEqual(sort_entries(input_data), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expecred=expected): + actual_result = sort_entries(input_data) + error_msg = (f'Called sort_entries({input_data}). ' + f'The function returned {actual_result}, but the tests ' + f'expected: {expected} for the sorted entries.') + + # Because we are asserting equal, we need to convert to an OrderedDict. + # Regular dictionaries will compare equal even when they are ordered + # differently from one another. See https://stackoverflow.com/a/58961124 + self.assertEqual(OrderedDict(actual_result), OrderedDict(expected), msg=error_msg) @pytest.mark.task(taskno=5) def test_send_to_store(self): input_data = [ ({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, - {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}), + {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], + 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}), ({'Kiwi': 3, 'Juice': 5, 'Yoghurt': 2, 'Milk': 5}, - {'Kiwi': ['Isle 6', False], 'Juice': ['Isle 5', False], 'Yoghurt': ['Isle 2', True], 'Milk': ['Isle 2', True]}), + {'Kiwi': ['Isle 6', False], 'Juice': ['Isle 5', False], + 'Yoghurt': ['Isle 2', True], 'Milk': ['Isle 2', True]}), + + ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, + 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4}, - ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4}, - {'Apple': ['Isle 1', False], 'Raspberry': ['Isle 6', False], 'Blueberries': ['Isle 6', False], - 'Broccoli': ['Isle 3', False], 'Kiwi': ['Isle 6', False], 'Melon': ['Isle 6', False]}) + {'Apple': ['Isle 1', False], 'Raspberry': ['Isle 6', False], + 'Blueberries': ['Isle 6', False], 'Broccoli': ['Isle 3', False], + 'Kiwi': ['Isle 6', False], 'Melon': ['Isle 6', False]}) ] output_data = [ - {'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, - {'Juice': [5, 'Isle 5', False], 'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True], 'Kiwi': [3, 'Isle 6', False]}, - {'Kiwi': [1, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], 'Apple': [2, 'Isle 1', False], - 'Raspberry': [2, 'Isle 6', False], 'Blueberries': [5, 'Isle 6', False], 'Broccoli': [2, 'Isle 3', False]} + {'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], + 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, + + {'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True], + 'Kiwi': [3, 'Isle 6', False], 'Juice': [5, 'Isle 5', False]}, + + {'Raspberry': [2, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], + 'Kiwi': [1, 'Isle 6', False], 'Broccoli': [2, 'Isle 3', False], + 'Blueberries': [5, 'Isle 6', False], 'Apple': [2, 'Isle 1', False]} ] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different fulfillment_cart instead.' - self.assertEqual(send_to_store(input_data[0], input_data[1]), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + actual_result = send_to_store(input_data[0], input_data[1]) + error_msg = (f'Called send_to_store({input_data[0]}, {input_data[1]}). ' + f'The function returned {actual_result}, but the tests ' + f'expected: {expected} as the fulfillment cart.') + + # Because we are asserting equal, we need to convert to an OrderedDict. + # Regular dictionaries will compare equal even when they are ordered + # differently from one another. See https://stackoverflow.com/a/58961124 + self.assertEqual(OrderedDict(actual_result), OrderedDict(expected), msg=error_msg) @pytest.mark.task(taskno=6) def test_update_store_inventory(self): @@ -155,7 +192,11 @@ def test_update_store_inventory(self): 'Blueberries': [5, 'Isle 6', False], 'Broccoli': [3, 'Isle 3', False]} ] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different store inventory instead.' - self.assertEqual(update_store_inventory(input_data[0], input_data[1]), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + actual_result = update_store_inventory(input_data[0], input_data[1]) + error_msg = (f'Called update_store_inventory({input_data[0]}, {input_data[1]}). ' + f'The function returned {actual_result}, but the tests ' + f'expected: {expected} as the store inventory.') + + self.assertEqual(actual_result, expected, msg=error_msg) From f4d59b748d5d7a41bc6223b50747397061f9c876 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:00:43 -0800 Subject: [PATCH 539/826] Updated test error messages. (#3545) [no important files changed] --- .../locomotive_engineer_test.py | 65 +++++++++++++------ 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py index d3d3178fdf..95faf9086c 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer_test.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer_test.py @@ -14,10 +14,15 @@ def test_get_list_of_wagons(self): input_data = [(1,5,2,7,4), (1,5), (1,), (1,9,3), (1,10,6,3,9,8,4,14,24,7)] output_data = [[1,5,2,7,4], [1,5], [1], [1,9,3], [1,10,6,3,9,8,4,14,24,7]] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different wagon list instead.' - self.assertEqual(get_list_of_wagons(*input_data), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + + actual_result = get_list_of_wagons(*input_data) + error_msg= (f'Called get_list_of_wagons{input_data}. ' + f'The function returned {actual_result}, but the ' + f'tests expected: {expected} as the wagon list instead.') + + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=2) def test_fix_list_of_wagons(self): @@ -31,10 +36,15 @@ def test_fix_list_of_wagons(self): [1, 8, 6, 15, 4, 2], [1, 8, 6, 4, 5, 9, 21, 2, 13, 25, 7, 19, 10, 3, 14] ] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different wagon list instead.' - self.assertEqual(fix_list_of_wagons(input_data[0], input_data[1]), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + + actual_result = fix_list_of_wagons(input_data[0], input_data[1]) + error_msg= (f'Called fix_list_of_wagons({input_data[0]}, {input_data[1]}). ' + f'The function returned {actual_result}, but the ' + f'tests expected: {expected} as the wagon list instead.') + + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=3) def test_add_missing_stops(self): @@ -48,10 +58,15 @@ def test_add_missing_stops(self): {'from': 'New York', 'to': 'Philadelphia', 'stops': []}, {'from': 'Gothenburg', 'to': 'Copenhagen', 'stops': ['Kungsbacka', 'Varberg', 'Halmstad', 'Angelholm', 'Lund', 'Malmo']} ] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different set of stops instead.' - self.assertEqual(add_missing_stops(input_data[0], **input_data[1]), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + + actual_result = add_missing_stops(input_data[0], **input_data[1]) + error_msg= (f'Called add_missing_stops({input_data[0]}, {input_data[1]}). ' + f'The function returned {actual_result}, but the ' + f'tests expected: {expected} as the set of stops.') + + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=4) def test_extend_route_information(self): @@ -63,10 +78,15 @@ def test_extend_route_information(self): {'from': 'Gothenburg', 'to': 'Copenhagen', 'precipitation': '1', 'timeOfArrival': '21:20', 'temperature': '-6'} ] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different route dictionary instead.' - self.assertEqual(extend_route_information(input_data[0], input_data[1]), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + + actual_result = extend_route_information(input_data[0], input_data[1]) + error_msg= (f'Called extend_route_information({input_data[0]}, {input_data[1]}). ' + f'The function returned {actual_result}, but the ' + f'tests expected: {expected} as the route dictionary.') + + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=5) def test_fix_wagon_depot(self): @@ -84,7 +104,12 @@ def test_fix_wagon_depot(self): [[(3, 'purple'), (20, 'black'), (19, 'white')], [(11, 'purple'), (16, 'black'), (17, 'white')], [(15, 'purple'), (12, 'black'), (18, 'white')]] ) - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data): - error_msg=f'Expected: {output_data} but got a different wagon depot list instead.' - self.assertEqual(fix_wagon_depot(input_data), output_data, msg=error_msg) + for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): + + actual_result = fix_wagon_depot(input_data) + error_msg= (f'Called fix_wagon_depot({input_data}). ' + f'The function returned {actual_result}, but the ' + f'tests expected: {expected} as the wagon depot list.') + + self.assertEqual(actual_result, expected, msg=error_msg) From cb35445b168f5cb727e5441ff7f4a6d4d73560d7 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:01:01 -0800 Subject: [PATCH 540/826] Changed test error messages and touched up docs. (#3544) [no important files changed] --- concepts/bools/about.md | 4 +- concepts/bools/introduction.md | 4 +- .../ghost-gobble-arcade-game/.docs/hints.md | 5 +- .../.docs/introduction.md | 4 +- .../.meta/config.json | 2 +- .../arcade_game_test.py | 162 +++++++++++------- 6 files changed, 111 insertions(+), 70 deletions(-) diff --git a/concepts/bools/about.md b/concepts/bools/about.md index 4b69727065..a2680fc06b 100644 --- a/concepts/bools/about.md +++ b/concepts/bools/about.md @@ -1,6 +1,6 @@ # About -Python represents True and False values with the [bool][bool] type. +Python represents true and false values with the [`bool`][bools] type, which is a subtype of `int`. There are only two Boolean values in this type: `True` and `False`. These values can be assigned to a variable and combined with the [Boolean operators][boolean-operators] (`and`, `or`, `not`): @@ -139,3 +139,5 @@ It is considered a [Python anti-pattern][comparing to true in the wrong way] to [Boolean-operators]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not [comparing to true in the wrong way]: https://docs.quantifiedcode.com/python-anti-patterns/readability/comparison_to_true.html [comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons + +[bools]: https://docs.python.org/3/library/stdtypes.html#typebool \ No newline at end of file diff --git a/concepts/bools/introduction.md b/concepts/bools/introduction.md index 535f90be07..af24137025 100644 --- a/concepts/bools/introduction.md +++ b/concepts/bools/introduction.md @@ -1,6 +1,6 @@ # Introduction -Python represents true and false values with the `bool` type. +Python represents true and false values with the [`bool`][bools] type, which is a subtype of `int`. There are only two values under that type: `True` and `False`. These values can be bound to a variable: @@ -21,3 +21,5 @@ We can evaluate Boolean expressions using the `and`, `or`, and `not` operators. >>> true_variable = not False >>> false_variable = not True ``` + +[bools]: https://docs.python.org/3/library/stdtypes.html#typebool \ No newline at end of file diff --git a/exercises/concept/ghost-gobble-arcade-game/.docs/hints.md b/exercises/concept/ghost-gobble-arcade-game/.docs/hints.md index 3cc88ff401..e31e3ed050 100644 --- a/exercises/concept/ghost-gobble-arcade-game/.docs/hints.md +++ b/exercises/concept/ghost-gobble-arcade-game/.docs/hints.md @@ -2,6 +2,7 @@ ## General +- For an overview, this section of the Python documentation: [Truth Value Testing][stdlib-bools] might help. - Don't worry about how the arguments are _derived_, focus on combining the arguments to return the intended result. ## 1. Define if Pac-Man can eat a ghost @@ -20,6 +21,6 @@ - You can use the [Boolean][boolean] [operators][Boolean-operators] to combine arguments for a result. -[boolean]: https://docs.python.org/3/library/stdtypes.html#truth [Boolean-operators]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not - +[boolean]: https://docs.python.org/3/library/stdtypes.html#truth +[stdlib-bools]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/concept/ghost-gobble-arcade-game/.docs/introduction.md b/exercises/concept/ghost-gobble-arcade-game/.docs/introduction.md index cf09a2cea0..a0743f7a11 100644 --- a/exercises/concept/ghost-gobble-arcade-game/.docs/introduction.md +++ b/exercises/concept/ghost-gobble-arcade-game/.docs/introduction.md @@ -1,6 +1,6 @@ # Introduction -Python represents true and false values with the `bool` type. +Python represents true and false values with the [`bool`][bools] type, which is a subtype of `int`. There are only two values in this type: `True` and `False`. These values can be bound to a variable: @@ -21,3 +21,5 @@ We can evaluate Boolean expressions using the `and`, `or`, and `not` operators: >>> true_variable = not False >>> false_variable = not True ``` + +[bools]: https://docs.python.org/3/library/stdtypes.html#typebool \ No newline at end of file diff --git a/exercises/concept/ghost-gobble-arcade-game/.meta/config.json b/exercises/concept/ghost-gobble-arcade-game/.meta/config.json index 320ea64677..9065b64bb2 100644 --- a/exercises/concept/ghost-gobble-arcade-game/.meta/config.json +++ b/exercises/concept/ghost-gobble-arcade-game/.meta/config.json @@ -4,7 +4,7 @@ ], "contributors": [ "cmccandless", - "bethanyg" + "BethanyG" ], "files": { "solution": [ diff --git a/exercises/concept/ghost-gobble-arcade-game/arcade_game_test.py b/exercises/concept/ghost-gobble-arcade-game/arcade_game_test.py index 6654c8ea3c..4dc3ca4c56 100644 --- a/exercises/concept/ghost-gobble-arcade-game/arcade_game_test.py +++ b/exercises/concept/ghost-gobble-arcade-game/arcade_game_test.py @@ -7,104 +7,138 @@ class GhostGobbleGameTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_ghost_gets_eaten(self): - self.assertIs( - eat_ghost(True, True), - True, - msg="ghost should get eaten" - ) + actual_result = eat_ghost(True, True) + error_message = ('Called eat_ghost(True, True).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ghost gets eaten (True).') + + self.assertIs(actual_result, True, msg=error_message) @pytest.mark.task(taskno=1) def test_ghost_does_not_get_eaten_because_no_power_pellet_active(self): - self.assertIs( - eat_ghost(False, True), - False, - msg="ghost does not get eaten because no power pellet active" - ) + actual_result = eat_ghost(False, True) + error_message = ('Called eat_ghost(False, True).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'ghost **does not** get eaten because ' + 'no power pellet was active.') + + self.assertIs(actual_result, False, msg=error_message) @pytest.mark.task(taskno=1) def test_ghost_does_not_get_eaten_because_not_touching_ghost(self): - self.assertIs( - eat_ghost(True, False), - False, - msg="ghost does not get eaten because not touching ghost" - ) + actual_result = eat_ghost(True, False) + error_message = ('Called eat_ghost(True, False).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'ghost **does not** get eaten because ' + 'the player was not touching the ghost.') + + self.assertIs(actual_result, False, msg=error_message) @pytest.mark.task(taskno=2) def test_score_when_eating_dot(self): - self.assertIs( - score(False, True), - True, - msg="score when eating dot" - ) + actual_result = score(False, True) + error_message = ('Called score(False, True).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'player scores because they were touching a dot.') + + self.assertIs(actual_result, True, msg=error_message) @pytest.mark.task(taskno=2) def test_score_when_eating_power_pellet(self): - self.assertIs( - score(True, False), - True, - msg="score when eating power pellet" - ) + actual_result = score(True, False) + error_message = ('Called score(True, False).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'player scores because they ' + 'were touching a power pellet.') + + self.assertIs(actual_result,True,msg=error_message) @pytest.mark.task(taskno=2) def test_no_score_when_nothing_eaten(self): - self.assertIs( - score(False, False), - False, - msg="no score when nothing eaten" - ) + actual_result = score(False, False) + error_message = ('Called score(False, False).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'player **does not** score because they ' + 'were not touching anything.') + self.assertIs(actual_result, False,msg=error_message) @pytest.mark.task(taskno=3) def test_lose_if_touching_a_ghost_without_a_power_pellet_active(self): + actual_result = lose(False, True) + error_message = ('Called lose(False, True).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'player loses because they touched a ' + 'ghost without a power pellet activated.') self.assertIs( - lose(False, True), - True, - msg="lose if touching a ghost without a power pellet active" - ) + actual_result, True, msg=error_message) @pytest.mark.task(taskno=3) def test_dont_lose_if_touching_a_ghost_with_a_power_pellet_active(self): - self.assertIs( - lose(True, True), - False, - msg="don't lose if touching a ghost with a power pellet active" - ) + actual_result = lose(True, True) + error_message = ('Called lose(True, True).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'player **does not** lose because when they touched a ' + 'ghost, a power pellet was active.') + + self.assertIs(actual_result, False, msg=error_message) @pytest.mark.task(taskno=3) def test_dont_lose_if_not_touching_a_ghost(self): - self.assertIs( - lose(True, False), - False, - msg="don't lose if not touching a ghost" - ) + actual_result = lose(True, False) + error_message = ('Called lose(True, False).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'player **does not** lose because they were ' + 'not touching a ghost.') + + self.assertIs(actual_result, False, msg=error_message) @pytest.mark.task(taskno=4) def test_win_if_all_dots_eaten(self): - self.assertIs( - win(True, False, False), - True, - msg="win if all dots eaten" - ) + actual_result = win(True, False, False) + error_message = ('Called win(True, False, False).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'player wins because all the dots were eaten.') + + self.assertIs(actual_result, True, msg=error_message) @pytest.mark.task(taskno=4) def test_dont_win_if_all_dots_eaten_but_touching_a_ghost(self): - self.assertIs( - win(True, False, True), - False, - msg="don't win if all dots eaten, but touching a ghost" - ) + actual_result = win(True, False, True) + error_message = ('Called win(True, False, True).' + f'The function returned {actual_result}, but the ' + f'tests expected that the ' + 'player **does not** win, because ' + 'the player was touching a ghost.') + + self.assertIs(actual_result, False, msg=error_message) @pytest.mark.task(taskno=4) def test_win_if_all_dots_eaten_and_touching_a_ghost_with_a_power_pellet_active(self): - self.assertIs( - win(True, True, True), - True, - msg="win if all dots eaten and touching a ghost with a power pellet active" - ) + actual_result = win(True, True, True) + error_message = ('Called win(True, True, True).' + f'The function returned {actual_result}, but the ' + f'tests expected that the player wins, ' + f'because a power pellet was active when they ' + f'touched a ghost.') + + self.assertIs(actual_result, True, msg=error_message) @pytest.mark.task(taskno=4) def test_dont_win_if_not_all_dots_eaten(self): - self.assertIs( - win(False, True, True), - False, - msg="don't win if not all dots eaten and touching a ghost with a power pellet active" - ) + actual_result = win(False, True, True) + error_message = ('Called win(False, True, True).' + f'The function returned {actual_result}, but the ' + f'tests expected that the player **does not** win, ' + f'because the player did not eat all of the dots.') + + self.assertIs(actual_result, False, msg=error_message) + From 2e407c75b814bde36a45f1b6e684319659e6ccf1 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:01:25 -0800 Subject: [PATCH 541/826] [Tisbury Treasure Hunt]: Modified Test Error Messages & Touched Up Docs (#3543) * Added error messages for tests and touched up docs. * Corrected username in config.json. [no important files changed] --- concepts/tuples/introduction.md | 4 +- .../tisbury-treasure-hunt/.docs/hints.md | 19 ++++--- .../.docs/introduction.md | 4 +- .../tisbury-treasure-hunt/.meta/config.json | 2 +- .../tisbury-treasure-hunt/tuples_test.py | 56 +++++++++++++------ 5 files changed, 54 insertions(+), 31 deletions(-) diff --git a/concepts/tuples/introduction.md b/concepts/tuples/introduction.md index 9ab6042e69..4271336863 100644 --- a/concepts/tuples/introduction.md +++ b/concepts/tuples/introduction.md @@ -7,6 +7,6 @@ The elements of a tuple can be iterated over using the `for item in ` con If both element index and value are needed, `for index, item in enumerate()` can be used. Like any sequence, elements within `tuples` can be accessed via _bracket notation_ using a `0-based index` number from the left or a `-1-based index` number from the right. -[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple [common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations -[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types \ No newline at end of file +[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types +[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple \ No newline at end of file diff --git a/exercises/concept/tisbury-treasure-hunt/.docs/hints.md b/exercises/concept/tisbury-treasure-hunt/.docs/hints.md index 4813c85cf7..55697511dc 100644 --- a/exercises/concept/tisbury-treasure-hunt/.docs/hints.md +++ b/exercises/concept/tisbury-treasure-hunt/.docs/hints.md @@ -3,9 +3,9 @@ ## General -[Tuples][tuples] are immutable [sequence Types][sequence types] that can contain any data type. -Tuples are [iterable][iterable], and elements within tuples can be accessed via [bracket notation][bracket notation], using a zero-based index from the left, or -1 from the right. -Other [Common Sequence Operations][common sequence operations] can also be used when working with tuples. +- [Tuples][tuples] are immutable [sequence Types][sequence types] that can contain any data type. +- Tuples are [iterable][iterable]. If you need indexes as well as values, use [`enumerate()`][enumerate] +- Elements within tuples can be accessed via [bracket notation][bracket notation], using a zero-based index from the left, or -1 from the right. Other [Common Sequence Operations][common sequence operations] can also be used when working with tuples. ## 1. Extract coordinates @@ -35,14 +35,15 @@ Other [Common Sequence Operations][common sequence operations] can also be used - There are multiple textual formatting options available via Pythons [`format specification mini-language`][format specification mini-language]. -[tuples]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences -[sequence types]: https://docs.python.org/3/library/stdtypes.html#typesseq -[iterable]: https://docs.python.org/3/glossary.html#term-iterable [bracket notation]: https://stackoverflow.com/questions/30250282/whats-the-difference-between-the-square-bracket-and-dot-notations-in-python -[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations -[class tuple]: https://docs.python.org/3/library/stdtypes.html#tuple [class str]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str -[str.format]: https://docs.python.org/3/library/stdtypes.html#str.format +[class tuple]: https://docs.python.org/3/library/stdtypes.html#tuple +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[enumerate]: https://docs.python.org/3/library/functions.html#enumerate [f-strings]: https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals [format specification mini-language]: https://docs.python.org/3/library/string.html#format-specification-mini-language +[iterable]: https://docs.python.org/3/glossary.html#term-iterable +[sequence types]: https://docs.python.org/3/library/stdtypes.html#typesseq +[str.format]: https://docs.python.org/3/library/stdtypes.html#str.format [testing membership]: https://docs.python.org/3/reference/expressions.html#membership-test-operations +[tuples]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences diff --git a/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md b/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md index fa0776752b..1bfbe3e615 100644 --- a/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md +++ b/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md @@ -3,9 +3,11 @@ In Python, a [tuple][tuple] is an _immutable_ collection of items in _sequence_. Like most collections, `tuples` can hold any (or multiple) data type(s) -- including other `tuples`. Tuples support all [common sequence operations][common sequence operations], but **do not** support [mutable sequence operations][mutable sequence operations]. + The elements of a tuple can be iterated over using the `for item in ` construct. If both element index and value are needed, `for index, item in enumerate()` can be used. Like any sequence, elements within `tuples` can be accessed via _bracket notation_ using a `0-based index` number from the left or a `-1-based index` number from the right. +Tuples can also be copied in whole or in part using slice notation (_`[::]`_). ## Tuple Construction @@ -140,5 +142,5 @@ True ``` [common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple -[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types \ No newline at end of file diff --git a/exercises/concept/tisbury-treasure-hunt/.meta/config.json b/exercises/concept/tisbury-treasure-hunt/.meta/config.json index f3d1ad373f..6dba773bde 100644 --- a/exercises/concept/tisbury-treasure-hunt/.meta/config.json +++ b/exercises/concept/tisbury-treasure-hunt/.meta/config.json @@ -1,6 +1,6 @@ { "authors": [ - "bethanyg" + "BethanyG" ], "files": { "solution": [ diff --git a/exercises/concept/tisbury-treasure-hunt/tuples_test.py b/exercises/concept/tisbury-treasure-hunt/tuples_test.py index e0256057f3..6bd8a50c56 100644 --- a/exercises/concept/tisbury-treasure-hunt/tuples_test.py +++ b/exercises/concept/tisbury-treasure-hunt/tuples_test.py @@ -1,6 +1,10 @@ import unittest import pytest -from tuples import get_coordinate, convert_coordinate, compare_records, create_record, clean_up +from tuples import (get_coordinate, + convert_coordinate, + compare_records, + create_record, + clean_up) class TisburyTreasureTest(unittest.TestCase): @@ -22,15 +26,20 @@ def test_get_coordinate(self): ('Silver Seahorse', '4E')] result_data = ['2A', '4B', '1C', '6D', '7E', '7F', '6A', '8A', '5B', '8C', '1F', '3D', '4E'] - number_of_variants = range(1, len(input_data) + 1) - for variant, item, result in zip(number_of_variants, input_data, result_data): - with self.subTest(f'variation #{variant}', item=item, result=result): - self.assertEqual(get_coordinate(item), result) + for variant, (item, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', item=item, expected=expected): + actual_result = get_coordinate(item) + error_message = (f'Called get_coordinate({item}). ' + f'The function returned "{actual_result}", but ' + f'the tests expected "{expected}" as the coordinates.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_convert_coordinate(self): - input_data = ['2A', '4B', '1C', '6D', '7E', '7F', '6A', '8A', '5B', '8C', '1F', '3D', '4E'] + input_data = ['2A', '4B', '1C', '6D', '7E', '7F', + '6A', '8A', '5B', '8C', '1F', '3D', '4E'] result_data = [('2', 'A'), ('4', 'B'), ('1', 'C'), @@ -45,11 +54,14 @@ def test_convert_coordinate(self): ('3', 'D'), ('4', 'E')] - number_of_variants = range(1, len(input_data) + 1) + for variant, (item, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', item=item, expected=expected): + actual_result = convert_coordinate(item) + error_message = (f'Called convert_coordinate({item}). ' + f'The function returned {actual_result}, but the ' + f'tests expected {expected} as the converted coordinate.') - for variant, item, result in zip(number_of_variants, input_data, result_data): - with self.subTest(f'variation #{variant}', item=item, result=result): - self.assertEqual(convert_coordinate(item), result) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_compare_records(self): @@ -66,11 +78,15 @@ def test_compare_records(self): (('Carved Wooden Elephant', '8C'), ('Abandoned Lighthouse', ('4', 'B'), 'Blue')) ] result_data = [True, True, True, True, True, False, False, False, False, False] - number_of_variants = range(1, len(input_data) + 1) - for variant, item, result in zip(number_of_variants, input_data, result_data): - with self.subTest(f'variation #{variant}', item=item, result=result): - self.assertEqual(compare_records(item[0], item[1]), result) + for variant, (item, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', item=item, expected=expected): + actual_result = compare_records(item[0], item[1]) + error_message = (f'Called compare_records({item[0]}, {item[1]}). ' + f'The function returned {actual_result}, but the ' + f'tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_create_record(self): @@ -99,11 +115,15 @@ def test_create_record(self): 'not a match' ] - number_of_variants = range(1, len(input_data) + 1) + for variant, (item, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', item=item, expected=expected): + actual_result = create_record(item[0], item[1]) + error_message = (f'Called create_record({item[0]},{item[1]}). ' + f'The function returned ' + f'{actual_result}, but the tests expected ' + f'{expected} for the record.') - for variant, item, result in zip(number_of_variants, input_data, result_data): - with self.subTest(f'variation #{variant}', item=item, result=result): - self.assertEqual(create_record(item[0], item[1]), result) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=5) def test_clean_up(self): From c5bfa4c524a7432325c61f5c7c4cafb430483bf5 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:01:48 -0800 Subject: [PATCH 542/826] [Making the Grade]: Modified Test Error Messages & Touched Up Docs (#3542) * Updated test error messages and touched up docs. * Removed deep copy from imports, as it was unneeded. [no important files changed] --- concepts/loops/about.md | 14 +- .../concept/making-the-grade/.docs/hints.md | 20 +- .../making-the-grade/.docs/instructions.md | 17 +- .../making-the-grade/.docs/introduction.md | 12 +- .../concept/making-the-grade/loops_test.py | 195 +++++++++++------- 5 files changed, 157 insertions(+), 101 deletions(-) diff --git a/concepts/loops/about.md b/concepts/loops/about.md index 88bb71e619..0f39e733d0 100644 --- a/concepts/loops/about.md +++ b/concepts/loops/about.md @@ -235,17 +235,17 @@ The loop [`else` clause][loop else] is unique to Python and can be used for "wra 'Found an S, stopping iteration.' ``` -[loop else]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops -[range]: https://docs.python.org/3/library/stdtypes.html#range [break statement]: https://docs.python.org/3/reference/simple_stmts.html#the-break-statement +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations [continue statement]: https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement -[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement -[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing [enumerate]: https://docs.python.org/3/library/functions.html#enumerate -[iterator]: https://docs.python.org/3/glossary.html#term-iterator -[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations -[range is not an iterator]: https://treyhunner.com/2018/02/python-range-is-not-an-iterator/ [for statement]: https://docs.python.org/3/reference/compound_stmts.html#for [iterable]: https://docs.python.org/3/glossary.html#term-iterable +[iterator]: https://docs.python.org/3/glossary.html#term-iterator +[loop else]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops [next built-in]: https://docs.python.org/3/library/functions.html#next +[range is not an iterator]: https://treyhunner.com/2018/02/python-range-is-not-an-iterator/ +[range]: https://docs.python.org/3/library/stdtypes.html#range [stopiteration]: https://docs.python.org/3/library/exceptions.html#StopIteration +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing +[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement diff --git a/exercises/concept/making-the-grade/.docs/hints.md b/exercises/concept/making-the-grade/.docs/hints.md index 78c1358d60..3e8deff958 100644 --- a/exercises/concept/making-the-grade/.docs/hints.md +++ b/exercises/concept/making-the-grade/.docs/hints.md @@ -2,11 +2,11 @@ ## General -- `while` loops are used for _indefinite_ (uncounted) iteration -- `for` loops are used for _definite_, (counted) iteration. -- The keywords `break` and `continue` help customize loop behavior. -- `range(, stop, )` can be used to generate a sequence for a loop counter. -- The built-in `enumerate()` will return (``, ``) pairs to iterate over. +- [`while`][while-loops] loops are used for _indefinite_ (uncounted) iteration +- [`for`][for-loops] loops are used for _definite_, (counted) iteration. +- The keywords [`break` and `continue`][control flow] help customize loop behavior. +- [`range(, stop, )`][range] can be used to generate a sequence for a loop counter. +- The built-in [`enumerate()`][enumerate] will return (``, ``) pairs to iterate over. Also being familiar with the following can help with completing the tasks: @@ -47,11 +47,13 @@ Also being familiar with the following can help with completing the tasks: - There may be or may not be a student with a score of 100, and you can't return `[]` without checking **all** scores. - The [`control flow`][control flow] statements `continue` and `break` may be useful here to move past unwanted values. -[list]: https://docs.python.org/3/library/stdtypes.html#list -[str]: https://docs.python.org/3/library/stdtypes.html#str -[f-strings]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals [append and pop]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists -[enumerate]: https://docs.python.org/3/library/functions.html#enumerate [control flow]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops +[enumerate]: https://docs.python.org/3/library/functions.html#enumerate +[f-strings]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals +[for-loops]: https://docs.python.org/3/tutorial/controlflow.html#for-statements +[list]: https://docs.python.org/3/library/stdtypes.html#list [range]: https://docs.python.org/3/tutorial/controlflow.html#the-range-function [round]: https://docs.python.org/3/library/functions.html#round +[str]: https://docs.python.org/3/library/stdtypes.html#str +[while-loops]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement diff --git a/exercises/concept/making-the-grade/.docs/instructions.md b/exercises/concept/making-the-grade/.docs/instructions.md index 43b25420c0..2f0c6617ae 100644 --- a/exercises/concept/making-the-grade/.docs/instructions.md +++ b/exercises/concept/making-the-grade/.docs/instructions.md @@ -9,7 +9,7 @@ You decide to make things a little more interesting by putting together some fun While you can give "partial credit" on exam questions, overall exam scores have to be `int`s. So before you can do anything else with the class scores, you need to go through the grades and turn any `float` scores into `int`s. Lucky for you, Python has the built-in [`round()`][round] function you can use. -Create the function `round_scores()` that takes a `list` of `student_scores`. +Create the function `round_scores(student_scores)` that takes a `list` of `student_scores`. This function should _consume_ the input `list` and `return` a new list with all the scores converted to `int`s. The order of the scores in the resulting `list` is not important. @@ -22,10 +22,10 @@ The order of the scores in the resulting `list` is not important. ## 2. Non-Passing Students -As you were grading the exam, you noticed some students weren't performing as well as you'd hoped. +As you were grading the exam, you noticed some students weren't performing as well as you had hoped. But you were distracted, and forgot to note exactly _how many_ students. -Create the function `count_failed_students()` that takes a `list` of `student_scores`. +Create the function `count_failed_students(student_scores)` that takes a `list` of `student_scores`. This function should count up the number of students who don't have passing scores and return that count as an integer. A student needs a score greater than **40** to achieve a passing grade on the exam. @@ -39,7 +39,7 @@ A student needs a score greater than **40** to achieve a passing grade on the ex The teacher you're assisting wants to find the group of students who've performed "the best" on this exam. What qualifies as "the best" fluctuates, so you need to find the student scores that are **greater than or equal to** the current threshold. -Create the function `above_threshold()` taking `student_scores` (a `list` of grades), and `threshold` (the "top score" threshold) as parameters. +Create the function `above_threshold(student_scores)` taking `student_scores` (a `list` of grades), and `threshold` (the "top score" threshold) as parameters. This function should return a `list` of all scores that are `>=` to `threshold`. ```python @@ -49,10 +49,11 @@ This function should return a `list` of all scores that are `>=` to `threshold`. ## 4. Calculating Letter Grades -The teacher you're assisting likes to assign letter grades as well as numeric scores. +The teacher you are assisting likes to assign letter grades as well as numeric scores. Since students rarely score 100 on an exam, the "letter grade" lower thresholds are calculated based on the highest score achieved, and increment evenly between the high score and the failing threshold of **<= 40**. -Create the function `letter_grades()` that takes the "highest" score on the exam as a parameter, and returns a `list` of lower score thresholds for each "American style" grade interval: `["D", "C", "B", "A"]`. +Create the function `letter_grades(highest)` that takes the "highest" score on the exam as an argument, and returns a `list` of lower score thresholds for each "American style" grade interval: `["D", "C", "B", "A"]`. + ```python """Where the highest score is 100, and failing is <= 40. @@ -84,7 +85,7 @@ Create the function `letter_grades()` that takes the "highest" score on the exam You have a list of exam scores in descending order, and another list of student names also sorted in descending order by their exam scores. You would like to match each student name with their exam score and print out an overall class ranking. -Create the function `student_ranking()` with parameters `student_scores` and `student_names`. +Create the function `student_ranking(student_scores)` with parameters `student_scores` and `student_names`. Match each student name on the student_names `list` with their score from the student_scores `list`. You can assume each argument `list` will be sorted from highest score(er) to lowest score(er). The function should return a `list` of strings with the format `. : `. @@ -101,7 +102,7 @@ The function should return a `list` of strings with the format `. , ]` pair of the student who scored 100 on the exam. diff --git a/exercises/concept/making-the-grade/.docs/introduction.md b/exercises/concept/making-the-grade/.docs/introduction.md index ad425d9092..2ae6ea724d 100644 --- a/exercises/concept/making-the-grade/.docs/introduction.md +++ b/exercises/concept/making-the-grade/.docs/introduction.md @@ -172,13 +172,13 @@ The [`break`][break statement] (_like in many C-related languages_) keyword can 'loop broken.' ``` -[for statement]: https://docs.python.org/3/reference/compound_stmts.html#for -[range]: https://docs.python.org/3/library/stdtypes.html#range [break statement]: https://docs.python.org/3/reference/simple_stmts.html#the-break-statement +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations [continue statement]: https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement -[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement -[iterable]: https://docs.python.org/3/glossary.html#term-iterable -[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing [enumerate]: https://docs.python.org/3/library/functions.html#enumerate -[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[for statement]: https://docs.python.org/3/reference/compound_stmts.html#for +[iterable]: https://docs.python.org/3/glossary.html#term-iterable [next built-in]: https://docs.python.org/3/library/functions.html#next +[range]: https://docs.python.org/3/library/stdtypes.html#range +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing +[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement diff --git a/exercises/concept/making-the-grade/loops_test.py b/exercises/concept/making-the-grade/loops_test.py index 669ca80c5a..598e2b0ddf 100644 --- a/exercises/concept/making-the-grade/loops_test.py +++ b/exercises/concept/making-the-grade/loops_test.py @@ -1,5 +1,6 @@ import unittest import pytest + from loops import ( round_scores, count_failed_students, @@ -13,90 +14,142 @@ class MakingTheGradeTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_round_scores(self): - data = [ - ([], []), - ([.5], [0]), - ([1.5], [2]), - ( - [90.33, 40.5, 55.44, 70.05, 30.55, 25.45, 80.45, 95.3, 38.7, 40.3], - [90, 40, 55, 70, 31, 25, 80, 95, 39, 40]), - ( - [50, 36.03, 76.92, 40.7, 43, 78.29, 63.58, 91, 28.6, 88.0], - [50, 36, 77, 41, 43, 78, 64, 91, 29, 88])] - - for variant, (student_scores, result) in enumerate(data, start=1): - error_message = f'Expected: {result} but one or more {student_scores} were rounded incorrectly.' - with self.subTest(f'variation #{variant}', input=student_scores, output=result): - self.assertEqual(sorted(round_scores(student_scores)), sorted(result), msg=error_message) + + # Because we the input list can be mutated, the test data has been created + # as tuples, which we then convert to a list when the test runs. + # this makes accurate error messages easier to create. + test_data = [tuple(), + (.5,), + (1.5,), + (90.33, 40.5, 55.44, 70.05, 30.55, 25.45, 80.45, 95.3, 38.7, 40.3), + (50, 36.03, 76.92, 40.7, 43, 78.29, 63.58, 91, 28.6, 88.0)] + result_data = [[], + [0], + [2], + [90, 40, 55, 70, 31, 25, 80, 95, 39, 40], + [50, 36, 77, 41, 43, 78, 64, 91, 29, 88]] + + for variant, (student_scores, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', student_scores=student_scores, expected=expected): + + # Because the test_input is a tuple, it has to be converted to a list for the function call. + actual_result = round_scores(list(student_scores)) + error_message = (f'Called round_scores({list(student_scores)}). ' + f'The function returned {sorted(actual_result)} after sorting, but ' + f'the tests expected {sorted(expected)} after sorting. ' + f'One or more scores were rounded incorrectly.') + + # everything is sorted for easier comparison. + self.assertEqual(sorted(actual_result), sorted(expected), msg=error_message) @pytest.mark.task(taskno=2) def test_count_failed_students(self): - data = [ - ([89, 85, 42, 57, 90, 100, 95, 48, 70, 96], 0), - ([40, 40, 35, 70, 30, 41, 90], 4)] + test_data = [[89, 85, 42, 57, 90, 100, 95, 48, 70, 96], + [40, 40, 35, 70, 30, 41, 90]] + result_data = [0,4] + + for variant, (student_scores, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', + student_scores=student_scores, + expected=expected): - for variant, (student_scores, result) in enumerate(data, start=1): - error_message = f'Expected the count to be {result}, but the count was not calculated correctly.' - with self.subTest(f'variation #{variant}', input=student_scores, output=result): - self.assertEqual(count_failed_students(student_scores), result, msg=error_message) + actual_result = count_failed_students(student_scores) + error_message = (f'Called count_failed_students({student_scores}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'number of students who failed.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_above_threshold(self): - data = [ - (([40, 39, 95, 80, 25, 31, 70, 55, 40, 90], 98), []), - (([88, 29, 91, 64, 78, 43, 41, 77, 36, 50], 80), [88, 91]), - (([100, 89], 100), [100]), - (([88, 29, 91, 64, 78, 43, 41, 77, 36, 50], 78), [88, 91, 78]), - (([], 80), [])] - - for variant, (params, result) in enumerate(data, start=1): - error_message = f'Expected: {result} but the number of scores above the threshold is incorrect.' - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertEqual(above_threshold(*params), result, msg=error_message) + test_data = [([40, 39, 95, 80, 25, 31, 70, 55, 40, 90], 98), + ([88, 29, 91, 64, 78, 43, 41, 77, 36, 50], 80), + ([100, 89], 100), + ([88, 29, 91, 64, 78, 43, 41, 77, 36, 50], 78), + ([], 80)] + + result_data = [[], + [88, 91], + [100], + [88, 91, 78], + []] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = above_threshold(*params) + error_message = (f'Called above_threshold{params}. ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'scores that are above the threshold.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_letter_grades(self): - data = [ - (100, [41, 56, 71, 86]), - (97, [41, 55, 69, 83]), - (85, [41, 52, 63, 74]), - (92, [41, 54, 67, 80]), - (81, [41, 51, 61, 71])] - - for variant, (highest, result) in enumerate(data, start=1): - error_message = f'Expected: {result} but the grade thresholds for a high score of {highest} are incorrect.' - with self.subTest(f'variation #{variant}', input=highest, output=result): - self.assertEqual(letter_grades(highest), result, msg=error_message) + test_data = [100, 97, 85, 92, 81] + + result_data = [[41, 56, 71, 86], + [41, 55, 69, 83], + [41, 52, 63, 74], + [41, 54, 67, 80], + [41, 51, 61, 71]] + + for variant, (highest, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', highest=highest, expected=expected): + actual_result = letter_grades(highest) + error_message = (f'Called letter_grades({highest}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'letter grade cutoffs.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=5) def test_student_ranking(self): - data = [ - (([82], ['Betty']), ['1. Betty: 82']), - (([88, 73], ['Paul', 'Ernest']), ['1. Paul: 88', '2. Ernest: 73']), - ( - ([100, 98, 92, 86, 70, 68, 67, 60], ['Rui', 'Betty', 'Joci', 'Yoshi', 'Kora', 'Bern', 'Jan', 'Rose']), - ['1. Rui: 100', '2. Betty: 98', '3. Joci: 92', '4. Yoshi: 86', - '5. Kora: 70', '6. Bern: 68', '7. Jan: 67', '8. Rose: 60'])] - - for variant, (params, result) in enumerate(data, start=1): - error_message = f'Expected: {result} but the rankings were compiled incorrectly.' - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertEqual(student_ranking(*params), result, msg=error_message) + test_data = [([82], ['Betty']), + ([88, 73], ['Paul', 'Ernest']), + ([100, 98, 92, 86, 70, 68, 67, 60], + ['Rui', 'Betty', 'Joci', 'Yoshi', 'Kora', 'Bern', 'Jan', 'Rose'])] + + result_data = [['1. Betty: 82'], + ['1. Paul: 88', '2. Ernest: 73'], + ['1. Rui: 100', '2. Betty: 98', '3. Joci: 92', '4. Yoshi: 86', + '5. Kora: 70', '6. Bern: 68', '7. Jan: 67', '8. Rose: 60']] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = student_ranking(*params) + error_message = (f'Called student_ranking{params}. ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'student rankings.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=6) def test_perfect_score(self): - data = [ - ([['Joci', 100], ['Vlad', 100], ['Raiana', 100], ['Alessandro', 100]], ['Joci', 100]), - ([['Jill', 30], ['Paul', 73], ], []), - ([], []), - ( - [['Rui', 60], ['Joci', 58], ['Sara', 91], ['Kora', 93], ['Alex', 42], - ['Jan', 81], ['Lilliana', 40], ['John', 60], ['Bern', 28], ['Vlad', 55]], []), - ( - [['Yoshi', 52], ['Jan', 86], ['Raiana', 100], ['Betty', 60], - ['Joci', 100], ['Kora', 81], ['Bern', 41], ['Rose', 94]], ['Raiana', 100])] - - for variant, (student_info, result) in enumerate(data, start=1): - error_message = f'Expected: {result} but got something different for perfect scores.' - with self.subTest(f'variation #{variant}', input=student_info, output=result): - self.assertEqual(perfect_score(student_info), result, msg=error_message) + test_data = [ + [['Joci', 100], ['Vlad', 100], ['Raiana', 100], ['Alessandro', 100]], + [['Jill', 30], ['Paul', 73]], + [], + [['Rui', 60], ['Joci', 58], ['Sara', 91], ['Kora', 93], ['Alex', 42], + ['Jan', 81], ['Lilliana', 40], ['John', 60], ['Bern', 28], ['Vlad', 55]], + + [['Yoshi', 52], ['Jan', 86], ['Raiana', 100], ['Betty', 60], + ['Joci', 100], ['Kora', 81], ['Bern', 41], ['Rose', 94]] + ] + + + result_data = [['Joci', 100],[], [], [], ['Raiana', 100]] + + for variant, (student_info, expected) in enumerate(zip(test_data, result_data), start=1): + + with self.subTest(f'variation #{variant}', student_info=student_info, expected=expected): + actual_result = perfect_score(student_info) + error_message = (f'Called perfect_score({student_info}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} for the ' + 'first "perfect" score.') + + self.assertEqual(actual_result, expected, msg=error_message) From b1fc9e81be225fb9724ee1f0c9e7854144b74c93 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:02:13 -0800 Subject: [PATCH 543/826] Changed test error messages and touched up docs. (#3541) [no important files changed] --- .../little-sisters-vocab/.docs/hints.md | 18 ++-- .../.docs/instructions.md | 8 +- .../.docs/introduction.md | 1 - .../little-sisters-vocab/.meta/config.json | 2 +- .../little-sisters-vocab/strings_test.py | 98 ++++++++++++------- 5 files changed, 79 insertions(+), 48 deletions(-) diff --git a/exercises/concept/little-sisters-vocab/.docs/hints.md b/exercises/concept/little-sisters-vocab/.docs/hints.md index f5e01ed3ea..2e5540805c 100644 --- a/exercises/concept/little-sisters-vocab/.docs/hints.md +++ b/exercises/concept/little-sisters-vocab/.docs/hints.md @@ -2,9 +2,9 @@ ## General -- The [Python Docs Tutorial for strings][python-str-doc] has an overview of the Python `str` type. -- String methods [.join()][str-join] and [.split()][str-split] ar very helpful when processing strings. -- The [Python Docs on Sequence Types][common sequence operations] has a rundown of operations common to all sequences, including `strings`, `lists`, `tuples`, and `ranges`. +- The Python Docs [Tutorial for strings][python-str-doc] has an overview of the Python `str` type. +- String methods [`str.join()`][str-join] and [`str.split()`][str-split] ar very helpful when processing strings. +- The Python Docs on [Sequence Types][common sequence operations] has a rundown of operations common to all sequences, including `strings`, `lists`, `tuples`, and `ranges`. There's four activities in the assignment, each with a set of text or words to work with. @@ -14,24 +14,24 @@ There's four activities in the assignment, each with a set of text or words to w ## 2. Add prefixes to word groups -- Believe it or not, `.join()` is all you need. -- Like `.split()`, `.join()` can take an arbitrary-length string, made up of any unicode code points. +- Believe it or not, [`str.join()`][str-join] is all you need here. +- Like [`str.split()`][str-split]`, `str.join()` can take an arbitrary-length string, made up of any unicode code points. ## 3. Remove a suffix from a word -- Strings can be both indexed and sliced from either the left (starting at 0) or the right (starting at -1). +- Strings can be indexed or sliced from either the left (starting at 0) or the right (starting at -1). - If you want the last code point of an arbitrary-length string, you can use [-1]. - The last three letters in a string can be "sliced off" using a negative index. e.g. 'beautiful'[:-3] == 'beauti' ## 4. Extract and transform a word -- Using `.split()` returns a list of strings broken on white space. +- Using [`str.split()`][str-split] returns a `list` of strings broken on white space. - `lists` are sequences, and can be indexed. -- `.split()` can be direcly indexed. e.g. `'Exercism rocks!'.split()[0] == 'Exercism'` +- [`str.split()`][str-split] can be directly indexed: `'Exercism rocks!'.split()[0] == 'Exercism'` - Be careful of punctuation! Periods can be removed via slice: `'dark.'[:-1] == 'dark'` -[python-str-doc]: https://docs.python.org/3/tutorial/introduction.html#strings [common sequence operations]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str +[python-str-doc]: https://docs.python.org/3/tutorial/introduction.html#strings [str-join]: https://docs.python.org/3/library/stdtypes.html#str.join [str-split]: https://docs.python.org/3/library/stdtypes.html#str.split diff --git a/exercises/concept/little-sisters-vocab/.docs/instructions.md b/exercises/concept/little-sisters-vocab/.docs/instructions.md index 6100cdb162..2772ce18d1 100644 --- a/exercises/concept/little-sisters-vocab/.docs/instructions.md +++ b/exercises/concept/little-sisters-vocab/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -You are helping your younger sister with her English vocabulary homework, which she's finding very tedious. +You are helping your younger sister with her English vocabulary homework, which she is finding very tedious. Her class is learning to create new words by adding _prefixes_ and _suffixes_. Given a set of words, the teacher is looking for correctly transformed words with correct spelling by adding the prefix to the beginning or the suffix to the ending. @@ -12,7 +12,7 @@ There's four activities in the assignment, each with a set of text or words to w One of the most common prefixes in English is `un`, meaning "not". In this activity, your sister needs to make negative, or "not" words by adding `un` to them. -Implement the `add_prefix_un()` function that takes `word` as a parameter and returns a new `un` prefixed word: +Implement the `add_prefix_un()` function that takes `word` as a parameter and returns a new `un` prefixed word: ```python @@ -63,7 +63,7 @@ Implement the `make_word_groups()` function that takes a `vocab_wor But of course there are pesky spelling rules: If the root word originally ended in a consonant followed by a 'y', then the 'y' was changed to 'i'. Removing 'ness' needs to restore the 'y' in those root words. e.g. `happiness` --> `happi` --> `happy`. -Implement the `remove_suffix_ness()` function that takes in a word `str`, and returns the root word without the `ness` suffix. +Implement the `remove_suffix_ness()` function that takes in a `word`, and returns the root word without the `ness` suffix. ```python @@ -76,7 +76,7 @@ Implement the `remove_suffix_ness()` function that takes in a word `str`, ## 4. Extract and transform a word -Suffixes are often used to change the part of speech a word has. +Suffixes are often used to change the part of speech a word is assigned to. A common practice in English is "verbing" or "verbifying" -- where an adjective _becomes_ a verb by adding an `en` suffix. In this task, your sister is going to practice "verbing" words by extracting an adjective from a sentence and turning it into a verb. diff --git a/exercises/concept/little-sisters-vocab/.docs/introduction.md b/exercises/concept/little-sisters-vocab/.docs/introduction.md index 220b9b73eb..7aaea474ee 100644 --- a/exercises/concept/little-sisters-vocab/.docs/introduction.md +++ b/exercises/concept/little-sisters-vocab/.docs/introduction.md @@ -228,4 +228,3 @@ Strings support all [common sequence operations][common sequence operations]. [str-split]: https://docs.python.org/3/library/stdtypes.html#str.split [text sequence]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str [unicode code points]: https://stackoverflow.com/questions/27331819/whats-the-difference-between-a-character-a-code-point-a-glyph-and-a-grapheme - diff --git a/exercises/concept/little-sisters-vocab/.meta/config.json b/exercises/concept/little-sisters-vocab/.meta/config.json index c18c248b8c..2e1cd930f5 100644 --- a/exercises/concept/little-sisters-vocab/.meta/config.json +++ b/exercises/concept/little-sisters-vocab/.meta/config.json @@ -1,7 +1,7 @@ { "authors": [ "aldraco", - "bethanyg" + "BethanyG" ], "files": { "solution": [ diff --git a/exercises/concept/little-sisters-vocab/strings_test.py b/exercises/concept/little-sisters-vocab/strings_test.py index 79ca4abc94..b13d4e9a35 100644 --- a/exercises/concept/little-sisters-vocab/strings_test.py +++ b/exercises/concept/little-sisters-vocab/strings_test.py @@ -12,65 +12,93 @@ class LittleSistersVocabTest(unittest.TestCase): def test_add_prefix_un(self): input_data = ['happy', 'manageable', 'fold', 'eaten', 'avoidable', 'usual'] result_data = [f'un{item}' for item in input_data] - number_of_variants = range(1, len(input_data) + 1) - for variant, word, result in zip(number_of_variants, input_data, result_data): - with self.subTest(f'variation #{variant}', word=word, result=result): - self.assertEqual(add_prefix_un(word), result, - msg=f'Expected: {result} but got a different word instead.') + for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', word=word, expected=expected): + + actual_result = add_prefix_un(word) + error_message = (f'Called add_prefix_un("{word}"). ' + f'The function returned "{actual_result}", but the ' + f'tests expected "{expected}" after adding "un" as a prefix.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_make_word_groups_en(self): input_data = ['en', 'circle', 'fold', 'close', 'joy', 'lighten', 'tangle', 'able', 'code', 'culture'] - result_data = ('en :: encircle :: enfold :: enclose :: enjoy :: enlighten ::' + expected = ('en :: encircle :: enfold :: enclose :: enjoy :: enlighten ::' ' entangle :: enable :: encode :: enculture') - self.assertEqual(make_word_groups(input_data), result_data, - msg=f'Expected {result_data} but got something else instead.') + actual_result = make_word_groups(input_data) + error_message = (f'Called make_word_groups({input_data}). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the ' + 'word groups.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_make_word_groups_pre(self): input_data = ['pre', 'serve', 'dispose', 'position', 'requisite', 'digest', 'natal', 'addressed', 'adolescent', 'assumption', 'mature', 'compute'] - result_data = ('pre :: preserve :: predispose :: preposition :: prerequisite :: ' - 'predigest :: prenatal :: preaddressed :: preadolescent :: preassumption :: ' - 'premature :: precompute') + expected = ('pre :: preserve :: predispose :: preposition :: prerequisite :: ' + 'predigest :: prenatal :: preaddressed :: preadolescent :: preassumption :: ' + 'premature :: precompute') + + actual_result = make_word_groups(input_data) + error_message = (f'Called make_word_groups({input_data}). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the ' + 'word groups.') - self.assertEqual(make_word_groups(input_data), result_data, - msg=f'Expected {result_data} but got something else instead.') + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_make_word_groups_auto(self): input_data = ['auto', 'didactic', 'graph', 'mate', 'chrome', 'centric', 'complete', 'echolalia', 'encoder', 'biography'] - result_data = ('auto :: autodidactic :: autograph :: automate :: autochrome :: ' - 'autocentric :: autocomplete :: autoecholalia :: autoencoder :: ' - 'autobiography') + expected = ('auto :: autodidactic :: autograph :: automate :: autochrome :: ' + 'autocentric :: autocomplete :: autoecholalia :: autoencoder :: ' + 'autobiography') - self.assertEqual(make_word_groups(input_data), result_data, - msg=f'Expected {result_data} but got something else instead.') + actual_result = make_word_groups(input_data) + error_message = (f'Called make_word_groups({input_data}). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the ' + 'word groups.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_make_words_groups_inter(self): input_data = ['inter', 'twine', 'connected', 'dependent', 'galactic', 'action', 'stellar', 'cellular', 'continental', 'axial', 'operative', 'disciplinary'] - result_data = ('inter :: intertwine :: interconnected :: interdependent :: ' - 'intergalactic :: interaction :: interstellar :: intercellular :: ' - 'intercontinental :: interaxial :: interoperative :: interdisciplinary') + expected = ('inter :: intertwine :: interconnected :: interdependent :: ' + 'intergalactic :: interaction :: interstellar :: intercellular :: ' + 'intercontinental :: interaxial :: interoperative :: interdisciplinary') + + actual_result = make_word_groups(input_data) + error_message = (f'Called make_word_groups({input_data}). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the ' + 'word groups.') - self.assertEqual(make_word_groups(input_data), result_data, - msg=f'Expected {result_data} but got something else instead.') + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_remove_suffix_ness(self): input_data = ['heaviness', 'sadness', 'softness', 'crabbiness', 'lightness', 'artiness', 'edginess'] result_data = ['heavy', 'sad', 'soft', 'crabby', 'light', 'arty', 'edgy'] - number_of_variants = range(1, len(input_data) + 1) - for variant, word, result in zip(number_of_variants, input_data, result_data): - with self.subTest(f'variation #{variant}', word=word, result=result): - self.assertEqual(remove_suffix_ness(word), result, - msg=f'Expected: {result} but got a different word instead.') + for variant, (word, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', word=word, expected=expected): + actual_result = remove_suffix_ness(word) + error_message = (f'Called remove_suffix_ness("{word}"). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" after the ' + 'suffix was removed.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_adjective_to_verb(self): @@ -86,9 +114,13 @@ def test_adjective_to_verb(self): index_data = [-2, -1, 3, 3, -2, -3, 5, 2, 1] result_data = ['brighten', 'darken', 'harden', 'soften', 'lighten', 'dampen', 'shorten', 'weaken', 'blacken'] - number_of_variants = range(1, len(input_data) + 1) - for variant, sentence, index, result in zip(number_of_variants, input_data, index_data, result_data): - with self.subTest(f'variation #{variant}', sentence=sentence, index=index, result=result): - self.assertEqual(adjective_to_verb(sentence, index), result, - msg=f'Expected: {result} but got a different word instead.') + for variant, (sentence, index, expected) in enumerate(zip(input_data, index_data, result_data), start=1): + with self.subTest(f'variation #{variant}', sentence=sentence, index=index, expected=expected): + actual_result = adjective_to_verb(sentence, index) + error_message = (f'Called adjective_to_verb("{sentence}", {index}). ' + f'The function returned "{actual_result}", but the tests ' + f'expected "{expected}" as the verb for ' + f'the word at index {index}.') + + self.assertEqual(actual_result, expected, msg=error_message) From 4c7e68c5d80dfbccae3ac8f68dc5a042e0e1336a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:02:32 -0800 Subject: [PATCH 544/826] Edited the test error messages and touched up docs. (#3540) [no important files changed] --- concepts/string-methods/.meta/config.json | 2 +- concepts/string-methods/about.md | 1 + concepts/string-methods/links.json | 8 +- .../little-sisters-essay/.docs/hints.md | 10 ++- .../little-sisters-essay/.meta/config.json | 2 +- .../string_methods_test.py | 78 +++++++++++++++---- 6 files changed, 78 insertions(+), 23 deletions(-) diff --git a/concepts/string-methods/.meta/config.json b/concepts/string-methods/.meta/config.json index 4bb37fcda4..d429a73810 100644 --- a/concepts/string-methods/.meta/config.json +++ b/concepts/string-methods/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "The 'str' class provides many useful 'string-methods'. They can be used for cleaning, splitting, translating, or otherwise working with any 'str' object. Because strings are immutable, any functions or methods that operate on a 'str' will return a new instance or copy of the 'str' rather than modifying the original 'str' object.", "authors": ["kimolivia"], - "contributors": ["valentin-p", "bethanyg"] + "contributors": ["valentin-p", "BethanyG"] } diff --git a/concepts/string-methods/about.md b/concepts/string-methods/about.md index 9ca16ffa29..983e604e71 100644 --- a/concepts/string-methods/about.md +++ b/concepts/string-methods/about.md @@ -18,6 +18,7 @@ Some of the more commonly used `str` methods include: Being _immutable_, a `str` object's value in memory cannot change; methods that appear to modify a string return a new copy or instance of that `str` object. + [`.endswith()`][str-endswith] returns `True` if the string ends with ``, `False` otherwise. ```python diff --git a/concepts/string-methods/links.json b/concepts/string-methods/links.json index 2e6fc8460f..c153ea4453 100644 --- a/concepts/string-methods/links.json +++ b/concepts/string-methods/links.json @@ -1,18 +1,18 @@ [ { "url": "https://docs.python.org/3/library/stdtypes.html#string-methods", - "description": "string methods" + "description": "Python Documentation: string methods" }, { "url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations", - "description": "common sequence operations" + "description": "Python Documentation: common sequence operations" }, { "url": "https://realpython.com/python-strings/", - "description": "strings and character data in Python" + "description": "Real Python: strings and character data in Python" }, { "url": "https://www.programiz.com/python-programming/string", - "description": "more string operations and functions" + "description": "Programiz: more string operations and functions" } ] diff --git a/exercises/concept/little-sisters-essay/.docs/hints.md b/exercises/concept/little-sisters-essay/.docs/hints.md index 46871dbd62..e842b3ad8f 100644 --- a/exercises/concept/little-sisters-essay/.docs/hints.md +++ b/exercises/concept/little-sisters-essay/.docs/hints.md @@ -2,7 +2,8 @@ ## General -- [Introduction to string methods in Python][string-method-docs] +- [Python Documentation: String Methods][string-method-docs] +- [Python Documentation Tutorial: Text][tutorial-strings] ## 1. Capitalize the title of the paper @@ -20,8 +21,9 @@ - You can use [string methods][replace-method-docs] to replace words. -[string-method-docs]: https://docs.python.org/3/library/stdtypes.html#string-methods -[title-method-docs]: https://docs.python.org/3/library/stdtypes.html#str.title [endswith-method-docs]: https://docs.python.org/3/library/stdtypes.html#str.endswith -[strip-method-docs]: https://docs.python.org/3/library/stdtypes.html#str.strip [replace-method-docs]: https://docs.python.org/3/library/stdtypes.html#str.replace +[string-method-docs]: https://docs.python.org/3/library/stdtypes.html#string-methods +[strip-method-docs]: https://docs.python.org/3/library/stdtypes.html#str.strip +[title-method-docs]: https://docs.python.org/3/library/stdtypes.html#str.title +[tutorial-strings]: https://docs.python.org/3/tutorial/introduction.html#text diff --git a/exercises/concept/little-sisters-essay/.meta/config.json b/exercises/concept/little-sisters-essay/.meta/config.json index ea803001dd..5049238618 100644 --- a/exercises/concept/little-sisters-essay/.meta/config.json +++ b/exercises/concept/little-sisters-essay/.meta/config.json @@ -4,7 +4,7 @@ ], "contributors": [ "valentin-p", - "bethanyg" + "BethanyG" ], "files": { "solution": [ diff --git a/exercises/concept/little-sisters-essay/string_methods_test.py b/exercises/concept/little-sisters-essay/string_methods_test.py index a74ccd0b7c..98973142eb 100644 --- a/exercises/concept/little-sisters-essay/string_methods_test.py +++ b/exercises/concept/little-sisters-essay/string_methods_test.py @@ -10,37 +10,89 @@ class LittleSistersEssayTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_capitalize_word(self): - self.assertEqual(capitalize_title("canopy"), "Canopy") + + actual_result = capitalize_title("canopy") + expected = "Canopy" + error_message = (f'Called capitalize_title("canopy"). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the title.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=1) def test_capitalize_title(self): - self.assertEqual(capitalize_title("fish are cold blooded"), - "Fish Are Cold Blooded") + + actual_result = capitalize_title("fish are cold blooded") + expected = "Fish Are Cold Blooded" + error_message = (f'Called capitalize_title("fish are cold blooded"). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" for the title.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_sentence_ending(self): - self.assertEqual(check_sentence_ending("Snails can sleep for 3 years."), True) + + actual_result = check_sentence_ending("Snails can sleep for 3 years.") + expected = True + error_message = (f'Called check_sentence_ending("Snails can sleep for 3 years."). ' + f'The function returned {actual_result}, ' + f'but the tests expected {expected} for a period ending.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_sentence_ending_without_period(self): - self.assertEqual(check_sentence_ending("Fittonia are nice"), False) + + actual_result = check_sentence_ending("Fittonia are nice") + expected = False + error_message = (f'Called check_sentence_ending("Fittonia are nice"). ' + f'The function returned {actual_result}, ' + f'but the tests expected {expected} for a period ending.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_remove_extra_spaces_only_start(self): - self.assertEqual(clean_up_spacing(" A rolling stone gathers no moss"), - "A rolling stone gathers no moss") + + actual_result = clean_up_spacing(" A rolling stone gathers no moss") + expected = "A rolling stone gathers no moss" + error_message = (f'Called clean_up_spacing(" A rolling stone gathers no moss"). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" as a cleaned string.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_remove_extra_spaces(self): - self.assertEqual(clean_up_spacing(" Elephants can't jump. "), - "Elephants can't jump.") + + actual_result = clean_up_spacing(" Elephants can't jump. ") + expected = "Elephants can't jump." + error_message = ("Called clean_up_spacing(\" Elephants can't jump. \")" + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" as a cleaned string.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_replace_word_choice(self): - self.assertEqual(replace_word_choice("Animals are cool.", "cool", "awesome"), - "Animals are awesome.") + + actual_result = replace_word_choice("Animals are cool.", "cool", "awesome") + expected = "Animals are awesome." + error_message = ('Called replace_word_choice("Animals are cool.", "cool", "awesome"). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}" after the word replacement.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_replace_word_not_exist(self): - self.assertEqual(replace_word_choice("Animals are cool.", "small", "tiny"), - "Animals are cool.") + + actual_result = replace_word_choice("Animals are cool.", "small", "tiny") + expected = "Animals are cool." + error_message = ('Called replace_word_choice("Animals are cool.", "small", "tiny"). ' + f'The function returned "{actual_result}", ' + f'but the tests expected "{expected}", because the word ' + 'to be replaced is not in the sentence.') + + self.assertEqual(actual_result, expected, msg=error_message) From 7c23bdf784352e9468ee9af6027041071a830fca Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:02:57 -0800 Subject: [PATCH 545/826] Updated tests error messages and reformatted hints. (#3539) [no important files changed] --- .../inventory-management/.docs/hints.md | 43 +++++--- .../concept/inventory-management/dicts.py | 1 + .../inventory-management/dicts_test.py | 97 ++++++++++++++----- 3 files changed, 100 insertions(+), 41 deletions(-) diff --git a/exercises/concept/inventory-management/.docs/hints.md b/exercises/concept/inventory-management/.docs/hints.md index 751c5afa69..dbdfe09ae7 100644 --- a/exercises/concept/inventory-management/.docs/hints.md +++ b/exercises/concept/inventory-management/.docs/hints.md @@ -2,35 +2,48 @@ ## General -- [The Python Dictionary Tutorial](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) can be a great introduction. +- [The Python Dictionary Tutorial][dict-tutorial] can be a great place to start. +- The Python docs on [Mapping Types - dicts][dict docs] is also pretty helpful. ## 1. Create an inventory based on a list -- You need a [for loop](https://docs.python.org/3/tutorial/controlflow.html#for-statements) to iterate the list of items, then insert each item in the dictionary if missing and increment the item count using the dictionary accessor. -- You can use [`setdefault`](https://www.w3schools.com/python/ref_dictionary_setdefault.asp) to make sure the value is set before incrementing the count of the item. -- This function should [return a dict](https://www.w3schools.com/python/ref_keyword_return.asp). +- You need a [for loop][for-loop] to iterate the list of items, then insert each item in the dictionary if missing and increment the item count using the dictionary accessor. +- You can use [`dict.setdefault`][dict setdefault] to make sure the value is set before incrementing the count of the item. +- This function should [return][return-keyword] a dict]. ## 2. Add items from a list to an existing dictionary -- You need a [for loop](https://docs.python.org/3/tutorial/controlflow.html#for-statements) to iterate the list of items, then insert each item if not already in the dictionary and [increment](https://www.w3schools.com/python/gloss_python_assignment_operators.asp) the item count using the dictionary accessor. -- You can use [`setdefault`](https://www.w3schools.com/python/ref_dictionary_setdefault.asp) to make sure the value is set before incrementing the count of the item. +- You need a [for loop][for-loop] to iterate the list of items, then insert each item if not already in the dictionary and [increment][increment] the item count using the dictionary accessor. +- You can use [`dict.setdefault`][dict setdefault] to make sure the value is set before incrementing the count of the item. - The function `add_items` can be used by the `create_inventory` function with an empty dictionary in parameter. -- This function should [return a dict](https://www.w3schools.com/python/ref_keyword_return.asp). +- This function should [return][return-keyword] a dict. ## 3. Decrement items from the inventory -- You need [for loop](https://docs.python.org/3/tutorial/controlflow.html#for-statements) to iterate the list of items, if the number of items is not `0` then [decrement](https://www.w3schools.com/python/gloss_python_assignment_operators.asp) the current number of items. -- You can use the `key in dict` that returns `True` if the key exists to make sure the value is in the dictionary before decrementing the number of items. -- This function should [return a dict](https://www.w3schools.com/python/ref_keyword_return.asp). +- You need [for loop][for-loop] to iterate the list of items, if the number of items is not `0` then [decrement][decrement] the current number of items. +- You can use the check `key in dict` that returns `True` if the key exists to make sure the value is in the dictionary before decrementing the number of items. +- This function should [return][return-keyword] a dict. ## 4. Remove an item entirely from the inventory -- If item is in the dictionary, [remove it](https://www.w3schools.com/python/ref_dictionary_pop.asp). +- If item is in the dictionary, [remove it][dict-pop]. - If item is not in the dictionary, do nothing. -- This function should [return a dict](https://www.w3schools.com/python/ref_keyword_return.asp). +- This function should [return][return-keyword] a dict. ## 5. Return the inventory content -- You need [for loop](https://docs.python.org/3/tutorial/controlflow.html#for-statements) on the inventory and if the number of item is greater of `0` then append the tuple to a list. -- You can use `dict.items()` to iterate on both the item and the value at the same time, `items()` returns a tuple that you can use as it is or deconstruct. -- This function should [return](https://www.w3schools.com/python/ref_keyword_return.asp) a [list](https://docs.python.org/3/tutorial/introduction.html#lists) of [tuples](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences). +- You need to use a [for loop][for-loop] on the inventory and if the number of item is greater of `0` then append the `tuple` to a `list`. +- You can use [`dict.items()`][dict items] to iterate on both the item and the value at the same time, `items()` returns a `tuple` that you can use or deconstruct, if needed. +- This function should [return][return-keyword] a [list][list] of [tuples][tuples]. + +[decrement]: https://www.w3schools.com/python/gloss_python_assignment_operators.asp +[dict docs]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[dict items]: https://docs.python.org/3/library/stdtypes.html#dict.items +[dict setdefault]: https://www.w3schools.com/python/ref_dictionary_setdefault.asp +[dict-pop]: https://www.w3schools.com/python/ref_dictionary_pop.asp +[dict-tutorial]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[for-loop]: https://docs.python.org/3/tutorial/controlflow.html#for-statements +[increment]: https://www.w3schools.com/python/gloss_python_assignment_operators.asp +[list]: https://docs.python.org/3/tutorial/introduction.html#lists +[return-keyword]: https://www.w3schools.com/python/ref_keyword_return.asp +[tuples]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences diff --git a/exercises/concept/inventory-management/dicts.py b/exercises/concept/inventory-management/dicts.py index d8f0ea2e81..ba524bba8c 100644 --- a/exercises/concept/inventory-management/dicts.py +++ b/exercises/concept/inventory-management/dicts.py @@ -52,3 +52,4 @@ def list_inventory(inventory): """ pass + diff --git a/exercises/concept/inventory-management/dicts_test.py b/exercises/concept/inventory-management/dicts_test.py index f9243e3f25..f7faba35a5 100644 --- a/exercises/concept/inventory-management/dicts_test.py +++ b/exercises/concept/inventory-management/dicts_test.py @@ -1,62 +1,107 @@ import unittest import pytest -from dicts import create_inventory, add_items, decrement_items, remove_item, list_inventory +from dicts import (create_inventory, + add_items, + decrement_items, + remove_item, + list_inventory) class InventoryTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_create_inventory(self): - self.assertEqual(create_inventory(["wood", "iron", "iron", "diamond", "diamond"]), - {"wood": 1, "iron": 2, "diamond": 2}) + + actual_result = create_inventory(["wood", "iron", "iron", "diamond", "diamond"]) + expected = {"wood": 1, "iron": 2, "diamond": 2} + error_message = ('Called create_inventory(["wood", "iron", "iron", "diamond", "diamond"]). ' + f'The function returned {actual_result}, but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_add_one_item(self): - self.assertEqual(add_items({"wood": 4, "iron": 2}, ["iron", "iron"]), - {"wood": 4, "iron": 4}) + actual_result = add_items({"wood": 4, "iron": 2}, ["iron", "iron"]) + expected = {"wood": 4, "iron": 4} + error_message = ('Called add_items({"wood": 4, "iron": 2}, ["iron", "iron"]). ' + f'The function returned {actual_result}, but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_add_multiple_items(self): - self.assertEqual(add_items({"wood": 2, "gold": 1, "diamond": 3}, ["wood", "gold", "gold"]), - {"wood": 3, "gold": 3, "diamond": 3}) + actual_result = add_items({"wood": 2, "gold": 1, "diamond": 3}, ["wood", "gold", "gold"]) + expected = {"wood": 3, "gold": 3, "diamond": 3} + error_message = ('Called add_items({"wood": 2, "gold": 1, "diamond": 3}, ["wood", "gold", "gold"]). ' + f'The function returned {actual_result}, but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_add_new_item(self): - self.assertEqual(add_items({"iron": 1, "diamond": 2}, ["iron", "wood", "wood"]), - {"iron": 2, "diamond": 2, "wood": 2}) + actual_result = add_items({"iron": 1, "diamond": 2}, ["iron", "wood", "wood"]) + expected = {"iron": 2, "diamond": 2, "wood": 2} + error_message = ('Called add_items({"iron": 1, "diamond": 2}, ["iron", "wood", "wood"]). ' + f'The function returned {actual_result}, but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_add_from_empty_dict(self): - self.assertEqual(add_items({}, ["iron", "iron", "diamond"]), - {"iron": 2, "diamond": 1}) + actual_result = add_items({}, ["iron", "iron", "diamond"]) + expected = {"iron": 2, "diamond": 1} + error_message = ('Called add_items({}, ["iron", "iron", "diamond"]). ' + f'The function returned {actual_result}, but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_decrement_items(self): - self.assertEqual(decrement_items({"iron": 3, "diamond": 4, "gold": 2}, - ["iron", "iron", "diamond", "gold", "gold"]), - {"iron": 1, "diamond": 3, "gold": 0}) + actual_result = decrement_items({"iron": 3, "diamond": 4, "gold": 2}, + ["iron", "iron", "diamond", "gold", "gold"]) + expected = {"iron": 1, "diamond": 3, "gold": 0} + error_message = ('Called decrement_items({"iron": 3, "diamond": 4, "gold": 2},' + '["iron", "iron", "diamond", "gold", "gold"]). The function ' + f'returned {actual_result}, but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_not_below_zero(self): - self.assertEqual(decrement_items({"wood": 2, "iron": 3, "diamond": 1}, - ["wood", "wood", "wood", "iron", "diamond", "diamond"]), - {"wood": 0, "iron": 2, "diamond": 0}) + actual_result = decrement_items({"wood": 2, "iron": 3, "diamond": 1}, + ["wood", "wood", "wood", "iron", "diamond", "diamond"]) + expected = {"wood": 0, "iron": 2, "diamond": 0} + error_message = ('Called decrement_items({"wood": 2, "iron": 3, "diamond": 1}, ' + '["wood", "wood", "wood", "iron", "diamond", "diamond"]). The ' + f'function returned {actual_result}, but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_remove_item(self): - self.assertEqual(remove_item({"iron": 1, "diamond": 2, "gold": 1}, "diamond"), - {"iron": 1, "gold": 1}) + actual_result = remove_item({"iron": 1, "diamond": 2, "gold": 1}, "diamond") + expected = {"iron": 1, "gold": 1} + error_message = ('Called remove_item({"iron": 1, "diamond": 2, "gold": 1}, "diamond"). ' + f'The function returned {actual_result}, but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_remove_item_not_in_inventory(self): - self.assertEqual(remove_item({"iron": 1, "diamond": 2, "gold": 1}, "wood"), - {"iron": 1, "gold": 1, "diamond": 2}) + actual_result = remove_item({"iron": 1, "diamond": 2, "gold": 1}, "wood") + expected = {"iron": 1, "gold": 1, "diamond": 2} + error_message = ('Called remove_item({"iron": 1, "diamond": 2, "gold": 1}, "wood"). ' + f'The function returned {actual_result}, ' + f'but the tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=5) def test_list_inventory(self): - self.assertEqual(list_inventory({"coal": 15, "diamond": 3, "wood": 67, "silver": 0}), - [("coal", 15), ("diamond", 3), ("wood", 67)]) - + actual_result = list_inventory({"coal": 15, "diamond": 3, "wood": 67, "silver": 0}) + expected = [("coal", 15), ("diamond", 3), ("wood", 67)] + error_message = ('Called list_inventory({"coal": 15, "diamond": 3, "wood": 67, "silver": 0}). ' + f'The function returned {actual_result}, ' + f'but the tests expected {expected}.') -if __name__ == "__main__": - unittest.main() + self.assertEqual(actual_result, expected, msg=error_message) From 7521cb030bbe306afd15db478a2cd0ebef4e77f6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:03:20 -0800 Subject: [PATCH 546/826] Updated test file error messages. (#3538) [no important files changed] --- .../concept/ellens-alien-game/classes_test.py | 198 ++++++++++++------ 1 file changed, 129 insertions(+), 69 deletions(-) diff --git a/exercises/concept/ellens-alien-game/classes_test.py b/exercises/concept/ellens-alien-game/classes_test.py index 3c38da78da..58b78d7096 100644 --- a/exercises/concept/ellens-alien-game/classes_test.py +++ b/exercises/concept/ellens-alien-game/classes_test.py @@ -3,73 +3,111 @@ try: - from classes import new_aliens_collection -except ImportError as err: - raise ImportError("We tried to import the new_aliens_collection() function, " - "but could not find it. Did you remember to create it?") from err + from classes import Alien +except ImportError as import_fail: + # pylint: disable=raise-missing-from + raise ImportError("\n\nMISSING CLASS --> We tried to import the 'Alien' class from " + "your classes.py file, but could not find it." + "Did you misname or forget to create it?") from None try: - from classes import Alien + from classes import new_aliens_collection except ImportError as err: - raise ImportError("We tried to import the 'Alien' class from the classes.py file, but could not find it. " - "Did you remember to create it?") from err + raise ImportError("\n\nMISSING FUNCTION --> We tried to import the " + "new_aliens_collection() function " + "from your classes.py file, but could not find it. " + "Did you misname or forget to create it?") from None class ClassesTest(unittest.TestCase): - # Test Alien class exists and correctly initialised. + @pytest.mark.task(taskno=1) def test_alien_has_correct_initial_coordinates(self): + """Test thst the Alien class gets correctly initialised.""" + alien = Alien(2, -1) - error = ("Expected object to be at position (2, -1) but instead " - f"found it initialized to position {(alien.x_coordinate, alien.y_coordinate)}.") + error_message = (f'Created a new Alien by calling Alien(2, -1). ' + f'The Alien was initialized to position ' + f'{(alien.x_coordinate, alien.y_coordinate)}, but the tests expected ' + f'the object to be at position (2, -1)') - self.assertEqual((2, -1), (alien.x_coordinate, alien.y_coordinate), msg=error) + self.assertEqual((2, -1), (alien.x_coordinate, alien.y_coordinate), msg=error_message) @pytest.mark.task(taskno=1) def test_alien_has_health(self): alien = Alien(0, 0) - error = ("Expected object's health to be 3 but instead found " - f"it had a health of {alien.health}.") + error_message = (f'Created a new Alien by calling Alien(0, 0). ' + f'The new Alien has a health of {alien.health}, ' + f'but the tests expect health = 3') - self.assertEqual(3, alien.health, msg=error) + self.assertEqual(3, alien.health, msg=error_message) - # Test instance variables are unique to specific instances. @pytest.mark.task(taskno=1) def test_alien_instance_variables(self): + """Test instance variables are unique to specific instances.""" + alien_one = Alien(-8, -1) alien_two = Alien(2, 5) - coord_x_error = ("Expected alien_one and alien_two to have different x " - f"positions. Instead both x's were: {alien_two.x_coordinate}.") - coord_y_error = ("Expected alien_one and alien_two to have different y " - f"positions. Instead both y's were: {alien_two.y_coordinate}.") + coord_x_error = (f'Created two new Aliens by assigning ' + f'alien_one = Alien(-8, -1) and alien_two = Alien(2, 5). ' + f'Both Aliens x coordinates were {alien_two.x_coordinate}, ' + f'but the tests expect alien_one and alien_two to have ' + f'different x positions.') + + coord_y_error = (f'Created two new Aliens by assigning ' + f'alien_one = Alien(-8, -1) and alien_two = Alien(2, 5). ' + f'Both Aliens y coordinates were {alien_two.y_coordinate}, ' + f'but the tests expect alien_one and alien_two to have ' + f'different y positions.') self.assertFalse(alien_one.x_coordinate == alien_two.x_coordinate, msg=coord_x_error) self.assertFalse(alien_one.y_coordinate == alien_two.y_coordinate, msg=coord_y_error) - # Test class methods work as specified. + @pytest.mark.task(taskno=2) def test_alien_hit_method(self): - #There are two valid interpretations for this method/task. - #`self.health -= 1` and `self.health = max(0, self.health - 1)` - #The tests for this task reflect this ambiguity. + """Test class methods work as specified. + + There are two valid interpretations for this method/task. + `self.health -= 1` and `self.health = max(0, self.health - 1)` + The tests for this task reflect this ambiguity. - data = [(1, (2,)), (2, (1,)), (3, (0,)), (4, (0, -1)), (5, (0, -2)), (6, (0, -3))] - for variant, (iterations, result) in enumerate(data, 1): + """ + + test_data = [1, 2, 3, 4, 5, 6] + result_data = [(2,), (1,), (0,), (0, -1), (0, -2), (0, -3)] + + for variant, (iterations, expected) in enumerate(zip(test_data, result_data), start=1): alien = Alien(2, 2) - with self.subTest(f'variation #{variant}', input=iterations, output=result): - error = ("Expected hit method to decrement health by 1. " - f"Health is {alien.health} when it should be {result}.") + + with self.subTest(f'variation #{variant}', + iterations=iterations, + expected=expected): + for _ in range(iterations): alien.hit() - self.assertIn(alien.health, result, msg=error) + + error_message = (f'Called hit() {iterations} time(s) ' + f'on a newly created Alien. The Aliens health ' + f'is now {alien.health}, but the tests expected ' + f'it to be in {expected} after decrementing 1 health ' + f'point {iterations} time(s).') + + self.assertIn(alien.health, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_alien_is_alive_method(self): alien = Alien(0, 1) - alive_error = "Alien is dead while health is greater than 0." - dead_error = "Alien is alive while health is less than or equal to 0." + + alive_error = ('Created a new Alien and called hit(). ' + 'The function is_alive() is returning False (dead) ' + 'while alien.health is greater than 0.') + + dead_error = ('Created a new Alien and called hit(). ' + 'The function is_alive() is returning True (alive) ' + 'while alien.health is less than or equal to 0.') for _ in range(5): alien.hit() @@ -83,70 +121,92 @@ def test_alien_teleport_method(self): alien = Alien(0, 0) alien.teleport(-1, -4) - error = ( - "Expected alien to be at position (-1, -4) but " - f"instead found it in position {(alien.x_coordinate, alien.y_coordinate)}.") + error_message = ('Called alien.teleport(-1,-4) on a newly created Alien. ' + 'The Alien was found at position ' + f'{(alien.x_coordinate, alien.y_coordinate)}, but the ' + 'tests expected it at position (-1, -4).') - self.assertEqual((-1, -4), (alien.x_coordinate, alien.y_coordinate), msg=error) + self.assertEqual((-1, -4), (alien.x_coordinate, alien.y_coordinate), msg=error_message) @pytest.mark.task(taskno=5) def test_alien_collision_detection_method(self): alien = Alien(7, 3) - error = "Expected collision_detection method to not be implemented." + error_message = ('Created a new Alien at (7,3) and called ' + 'alien.collision_detection(Alien(7, 2)). ' + f'The method returned {alien.collision_detection(Alien(7, 2))}, ' + 'but the tests expected None. ') + + self.assertIsNone(alien.collision_detection(Alien(7, 2)), msg=error_message) - self.assertIsNone(alien.collision_detection(Alien(7, 2)), msg=error) - # Test class variables are identical across instances @pytest.mark.task(taskno=6) def test_alien_class_variable(self): - alien_one = Alien(0, 2) - alien_two = Alien(-6, -1) - Alien.total_aliens_created = -2 + """Test class attribute/variables are identical across instances.""" + + alien_one, alien_two = Alien(0, 2), Alien(-6, -1) + Alien.health = 6 - error_one = "Expected the total_aliens_created variable to be identical." - error_two = "Expected the health variable to be identical." + created_error_message = ('Created two new Aliens and requested the ' + 'total_aliens_created attribute for each one. ' + f'Received {alien_one.total_aliens_created, alien_two.total_aliens_created} ' + f'for total_aliens_created, but the tests expect ' + f'the class attributes for each newly created Alien to be identical. ') - self.assertEqual(alien_two.total_aliens_created, alien_one.total_aliens_created, msg=error_one) - self.assertEqual(alien_two.health, alien_one.health, msg=error_two) + health_error_message = ('Created two new Aliens and requested the ' + f'health attribute for each one. Received {alien_one.health, alien_two.health} ' + 'for health, but the tests expect the class ' + 'attributes for each newly created Alien to be identical. ') + + self.assertEqual(alien_two.total_aliens_created, + alien_one.total_aliens_created, + msg=created_error_message) + + self.assertEqual(alien_two.health, + alien_one.health, + msg=health_error_message) - # Test total_aliens_created increments upon object instantiation @pytest.mark.task(taskno=6) def test_alien_total_aliens_created(self): + """Test total_aliens_created class variable increments upon object instantiation.""" + Alien.total_aliens_created = 0 aliens = [Alien(-2, 6)] - error = ("Expected total_aliens_created to equal 1. Instead " - f"it equals: {aliens[0].total_aliens_created}.") - self.assertEqual(1, aliens[0].total_aliens_created, msg=error) + error_message = ('Created a new Alien and called total_aliens_created for it. ' + f'{aliens[0].total_aliens_created} was returned, but ' + 'the tests expected that total_aliens_created would equal 1.') + + self.assertEqual(1, aliens[0].total_aliens_created, msg=error_message) aliens.append(Alien(3, 5)) aliens.append(Alien(-5, -5)) def error_text(alien, variable): - return ( - "Expected all total_aliens_created variables to be " - "equal to number of alien instances (i.e. 3). Alien " - f"number {alien}'s total_aliens_created variable " - f"is equal to {variable}.") + return ('Created two additional Aliens for the session.' + f"Alien number {alien}'s total_aliens_created variable " + f"is equal to {variable}, but the tests expected all " + 'total_aliens_created variables for all instances to be ' + 'equal to number of alien instances created (i.e. 3).') - tac_list = [alien.total_aliens_created for alien in aliens] + self.assertEqual(3, aliens[0].total_aliens_created, msg=error_text(1, aliens[0])) + self.assertEqual(3, aliens[1].total_aliens_created, msg=error_text(2, aliens[1])) + self.assertEqual(3, aliens[2].total_aliens_created, msg=error_text(3, aliens[2])) - self.assertEqual(3, tac_list[0], msg=error_text(1, tac_list[0])) - self.assertEqual(3, tac_list[1], msg=error_text(2, tac_list[1])) - self.assertEqual(3, tac_list[2], msg=error_text(3, tac_list[2])) - - # Test that the user knows how to create objects themselves @pytest.mark.task(taskno=7) def test_new_aliens_collection(self): - position_data = [(-2, 6), (1, 5), (-4, -3)] - obj_list = new_aliens_collection(position_data) - obj_error = "new_aliens_collection must return a list of Alien objects." + """Test that the user knows how to create objects themselves.""" + + test_data = [(-2, 6), (1, 5), (-4, -3)] + actual_result = new_aliens_collection(test_data) + + error_message = "new_aliens_collection() must return a list of Alien objects." - for obj, position in zip(obj_list, position_data): - self.assertIsInstance(obj, Alien, msg=obj_error) + for obj in actual_result: + self.assertIsInstance(obj, Alien, msg=error_message) - pos_error = ( - f"Expected object to be at position {position} but " - f"instead found it initialized to position {(obj.x_coordinate, obj.y_coordinate)}.") + for position, obj in zip(test_data, actual_result): + position_error = (f'After calling new_aliens_collection({test_data}), ' + f'found {obj} initialized to position {(obj.x_coordinate, obj.y_coordinate)}, ' + f'but the tests expected {obj} to be at position {position} instead.') - self.assertEqual(position, (obj.x_coordinate, obj.y_coordinate), msg=pos_error) + self.assertEqual(position, (obj.x_coordinate, obj.y_coordinate), msg=position_error) From 375818cf2332a4094b2c180a3742dc89a73a4243 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:03:57 -0800 Subject: [PATCH 547/826] [Currency Exchange]: Updated Test Error Messages. (#3537) * Updated test error messages. * Added bethang and kytrinyx as contributors. [no important files changed] --- .../currency-exchange/.meta/config.json | 2 + .../currency-exchange/exchange_test.py | 145 +++++++++++++----- 2 files changed, 109 insertions(+), 38 deletions(-) diff --git a/exercises/concept/currency-exchange/.meta/config.json b/exercises/concept/currency-exchange/.meta/config.json index c9825d3c60..a8188cdf9a 100644 --- a/exercises/concept/currency-exchange/.meta/config.json +++ b/exercises/concept/currency-exchange/.meta/config.json @@ -8,6 +8,8 @@ "J08K" ], "contributors": [ + "BethanyG", + "kytrinyx", "pranasziaukas" ], "files": { diff --git a/exercises/concept/currency-exchange/exchange_test.py b/exercises/concept/currency-exchange/exchange_test.py index 694c82d68c..fd3754cc19 100644 --- a/exercises/concept/currency-exchange/exchange_test.py +++ b/exercises/concept/currency-exchange/exchange_test.py @@ -1,5 +1,6 @@ import unittest import pytest + from exchange import ( exchange_money, get_change, @@ -10,63 +11,131 @@ class CurrencyExchangeTest(unittest.TestCase): - @pytest.mark.task(taskno=1) def test_exchange_money(self): - input_data = [(100000, 0.8), (700000, 10.0)] - output_data = [125000, 70000] + test_data = [(100000, 0.8), (700000, 10.0)] + result_data = [125000, 70000] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + budget, exchange_rate = params - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertAlmostEqual(exchange_money(input_data[0], input_data[1]), output_data) + with self.subTest(f"variation #{variant}", + budget=budget, + exchange_rate=exchange_rate, + expected=expected): + + actual_result = exchange_money(*params) + error_message = (f'Called exchange_money{budget, exchange_rate}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} when exchanging' + f' {budget} at a rate of {exchange_rate}.') + + self.assertAlmostEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_get_change(self): - input_data = [(463000, 5000), (1250, 120), (15000, 1380)] - output_data = [458000, 1130, 13620] + test_data = [(463000, 5000), (1250, 120), (15000, 1380)] + result_data = [458000, 1130, 13620] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + budget, exchanging_value = params - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertAlmostEqual(get_change(input_data[0], input_data[1]), output_data) + with self.subTest(f"variation #{variant}", + budget=budget, + exchanging_value=exchanging_value, + expected=expected): + + actual_result = get_change(*params) + error_message = (f'Called get_change{budget, exchanging_value}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} left in your budget.') + + self.assertAlmostEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_get_value_of_bills(self): - input_data = [(10000, 128), (50, 360), (200, 200)] - output_data = [1280000, 18000, 40000] + test_data = [(10000, 128), (50, 360), (200, 200)] + result_data = [1280000, 18000, 40000] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + denomination, number_of_bills = params + + with self.subTest(f"variation #{variant}", + denomination=denomination, + number_of_bills=number_of_bills, + expected=expected): + + actual_result = get_value_of_bills(*params) + error_message = (f'Called get_value_of_bills{denomination, number_of_bills}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} for the bills value.') - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertEqual(get_value_of_bills(input_data[0], input_data[1]), output_data) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_get_number_of_bills(self): - input_data = [(163270, 50000), (54361, 1000)] - output_data = [3, 54] + test_data = [(163270, 50000), (54361, 1000)] + result_data = [3, 54] - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertEqual(get_number_of_bills(input_data[0], input_data[1]), output_data) + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + amount, denomination = params + + with self.subTest(f"variation #{variant}", + amount=amount, + denomination=denomination, + expected=expected): + + actual_result = get_number_of_bills(amount, denomination) + error_message = (f'Called get_number_of_bills{amount, denomination}. ' + f'The function returned {actual_result} bills, but ' + f'The tests expected {expected} bills.') + + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=5) def test_get_leftover_of_bills(self): - input_data = [(10.1, 10), (654321.0, 5), (3.14, 2)] - output_data = [0.1, 1.0, 1.14] + test_data = [(10.1, 10), (654321.0, 5), (3.14, 2)] + result_data = [0.1, 1.0, 1.14] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + amount, denomination = params + + with self.subTest(f"variation #{variant}", + amount=amount, + denomination=denomination, + expected=expected): + + actual_result = get_leftover_of_bills(*params) + error_message = (f'Called get_leftover_of_bills{amount, denomination}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} as the leftover amount.') - for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1): - with self.subTest(f"variation #{variant}", input_data=input_data, output_data=output_data): - self.assertAlmostEqual(get_leftover_of_bills(input_data[0], input_data[1]), output_data) + self.assertAlmostEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=6) def test_exchangeable_value(self): - inputs = [ - (100000, 10.61, 10, 1), - (1500, 0.84, 25, 40), - (470000, 1050, 30, 10000000000), - (470000, 0.00000009, 30, 700), - (425.33, 0.0009, 30, 700)] - - output_data = [8568, 1400, 0, 4017094016600, 363300] - - for variant, (inputs, output_data) in enumerate(zip(inputs, output_data), start=1): - with self.subTest(f"variation #{variant}", inputs=inputs, output_data=output_data): - self.assertEqual(exchangeable_value(inputs[0], inputs[1], inputs[2], inputs[3]), output_data) + test_data = [(100000, 10.61, 10, 1), + (1500, 0.84, 25, 40), + (470000, 1050, 30, 10000000000), + (470000, 0.00000009, 30, 700), + (425.33, 0.0009, 30, 700)] + + result_data = [8568, 1400, 0, 4017094016600, 363300] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + budget, exchange_rate, spread, denomination = params + + with self.subTest(f"variation #{variant}", + budget=budget, + exchange_rate=exchange_rate, + spread=spread, + denomination=denomination, + expected=expected): + + actual_result = exchangeable_value(budget, exchange_rate, spread, denomination) + error_message = (f'Called exchangeable_value{budget, exchange_rate, spread, denomination}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} as the maximum ' + f'value of the new currency .') + + self.assertEqual(actual_result, expected, msg=error_message) From 31ced035f44a3cad1cda269f06341c9866e00651 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:04:16 -0800 Subject: [PATCH 548/826] Modified test error messages and did some light docs cleanup. (#3536) [no important files changed] --- .../chaitanas-colossal-coaster/.docs/hints.md | 14 +- .../.meta/config.json | 1 + .../list_methods_test.py | 196 +++++++++++------- 3 files changed, 128 insertions(+), 83 deletions(-) diff --git a/exercises/concept/chaitanas-colossal-coaster/.docs/hints.md b/exercises/concept/chaitanas-colossal-coaster/.docs/hints.md index bb38b47ee1..3616fc4fbf 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.docs/hints.md +++ b/exercises/concept/chaitanas-colossal-coaster/.docs/hints.md @@ -1,11 +1,13 @@ # General -Make sure you have a good understanding of how to create and update lists. +- Make sure you have a good understanding of how to create and update lists. +- The Python [documentation on `lists`][python lists] can be really helpful. +- The Python [tutorial section on `lists`][more on lists] is also a good resource. ## 1. Add Me to the queue -- You need to find the ticket type with an `if-else` statement. -- You can `append()` the person to the queue based on the ticket type. +- An `if-else` statement can help you find which ticket type you are dealing with. +- You can then `append()` the person to the queue based on the ticket type. ## 2. Where are my friends @@ -29,7 +31,9 @@ Make sure you have a good understanding of how to create and update lists. ## 7. Sort the Queue List -- Don't forget that You need to make a `copy()` of the queue to avoid mutating it and losing the original order. +- Don't forget that You need to avoid mutating the queue and losing its original order. - Once you have a `copy()`, `sort()`-ing should be straightforward. -- Order is alphabetical or _ascending_ sort. +- We're looking for an _ascending_ sort, or _alphabetical from a-z_. +[python lists]: https://docs.python.org/3.11/library/stdtypes.html#list +[more on lists]: https://docs.python.org/3.11/tutorial/datastructures.html#more-on-lists diff --git a/exercises/concept/chaitanas-colossal-coaster/.meta/config.json b/exercises/concept/chaitanas-colossal-coaster/.meta/config.json index 335d2f9d7d..5374c4d289 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.meta/config.json +++ b/exercises/concept/chaitanas-colossal-coaster/.meta/config.json @@ -4,6 +4,7 @@ "BethanyG" ], "contributors": [ + "BethanyG", "valentin-p", "pranasziaukas" ], diff --git a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py index 22f3d6bed6..c519ff255c 100644 --- a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py +++ b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py @@ -1,5 +1,6 @@ import unittest import pytest +from copy import deepcopy from list_methods import ( add_me_to_the_queue, @@ -15,107 +16,146 @@ class ListMethodsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue(self): - data = [ - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), - ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), - ] + test_data = [(['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), + (['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich')] + result_data = [['RobotGuy', 'WW', 'HawkEye'], + ['Tony', 'Bruce', 'RichieRich']] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + express_queue, normal_queue, ticket_type, person_name = deepcopy(params) + + with self.subTest(f'variation #{variant}', + express_queue=express_queue, + normal_queue=normal_queue, + ticket_type=ticket_type, + person_name=person_name, + expected=expected): - error_message = 'The person was not added to the queue correctly.' - for variant, (params, result) in enumerate(data, start=1): - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertListEqual(add_me_to_the_queue(*params), result, msg=error_message) + actual_result = add_me_to_the_queue(*params) + error_message = (f'Called add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} when adding ' + f'{person_name} to the queue.') + + self.assertListEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_find_my_friend(self): - data = [ - ((['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), 0), - ((['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), 1), - ((['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Rocket'), 4), - ] + test_data = [(['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Rocket')] + result_data = (0,1,4) + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = find_my_friend(*params) + error_message = (f'Called find_my_friend{params}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} when looking for ' + f'{params[-1]} in the queue.') - error_message = 'The index of the friend to find is incorrect.' - for variant, (params, result) in enumerate(data, start=1): - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertIs(find_my_friend(*params), result, msg=error_message) + self.assertIs(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_add_me_with_my_friends(self): - data = [ - ( - (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), - ['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'] - ), - ( + test_data = [(['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), - ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'] - ), - ( - (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky'), - ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky'] - ), - ] + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky')] + + result_data = [['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky']] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + queue, index, person_name = deepcopy(params) + + with self.subTest(f'variation #{variant}', + queue=queue, + index=index, + person_name=person_name, + expected=expected): + + actual_result = add_me_with_my_friends(*params) + error_message = (f'Called add_me_with_my_friends{queue, index, person_name}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} when adding ' + f'{person_name} to position {index} in the queue.') - error_message = 'The person was added to the wrong location in the queue or was not added at all.' - for variant, (params, result) in enumerate(data, start=1): - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertListEqual(add_me_with_my_friends(*params), result, error_message) + self.assertListEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_remove_the_mean_person(self): - data = [ - ( - (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), - ['Natasha', 'Steve', 'Wanda', 'Rocket'] - ), - ( - (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Ultron'), - ['Natasha', 'Steve', 'Wanda', 'Rocket'] - ), - ( - (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Ultron'), - ['Natasha', 'Steve', 'Wanda', 'Rocket'] - ), + test_data = [(['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), + (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Ultron'), + (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Ultron'), + ] + result_data = [['Natasha', 'Steve', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Wanda', 'Rocket']] + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + queue, person_name = deepcopy(params) - error_message = 'The mean person was not removed properly.' - for variant, (params, result) in enumerate(data, start=1): - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertListEqual(remove_the_mean_person(*params), result, msg=error_message) + with self.subTest(f'variation #{variant}', + queue=queue, + person_name=person_name, + expected=expected): + + actual_result = remove_the_mean_person(*params) + error_message = (f'Called remove_the_mean_person{queue, person_name}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} when removing ' + f'{person_name} from the queue.') + + self.assertListEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=5) def test_how_many_namefellows(self): - data = [ - ((['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Bucky'), 0), - ((['Natasha', 'Steve', 'Ultron', 'Rocket'], 'Natasha'), 1), - ((['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Natasha'), 2), - ] + test_data = [(['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Bucky'), + (['Natasha', 'Steve', 'Ultron', 'Rocket'], 'Natasha'), + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Natasha')] - error_message = 'The namefellow count is incorrect.' - for variant, (params, result) in enumerate(data, start=1): - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertIs(how_many_namefellows(*params), result, msg=error_message) + result_data = (0,1,2) + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = how_many_namefellows(*params) + + error_message = (f'Called how_many_namefellows{params}. ' + f'The function returned {actual_result}, but ' + f'The tests expected {expected} when counting ' + f'namefellows in the queue for {params[-1]}.') + + self.assertIs(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): - data = [ - (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Rocket'), - ] + test_data = ['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'] + actual_result = remove_the_last_person(test_data) + expected = 'Rocket' + error_message = (f'Called remove_the_last_person({test_data}).' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} as the person who was removed.') - error_message = 'The last person was not removed properly.' - for variant, (params, result) in enumerate(data, start=1): - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertIs(remove_the_last_person(params), result, msg=error_message) + self.assertIs(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=7) def test_sorted_names(self): - data = [ - ( - ['Steve', 'Ultron', 'Natasha', 'Rocket'], - ['Natasha', 'Rocket', 'Steve', 'Ultron'] - ), - ] - - error_message = 'The queue was not properly sorted.' - for variant, (params, result) in enumerate(data, start=1): - with self.subTest(f'variation #{variant}', input=params, output=result): - self.assertListEqual(sorted_names(params), result, msg=error_message) + test_data = ['Steve', 'Ultron', 'Natasha', 'Rocket'] + expected = ['Natasha', 'Rocket', 'Steve', 'Ultron'] + actual_result = sorted_names(test_data) + error_message = (f'Called sorted_names({test_data}).' + f'The function returned {actual_result}, but the tests ' + f'expected {expected}.') + + self.assertListEqual(actual_result, expected, msg=error_message) From 3445ffd32377959114c66dfcfb2c8a801a1cb269 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:04:37 -0800 Subject: [PATCH 549/826] Edited test error messages to include function calls and params. Edited docs for spelling and small grammar fixes. (#3528) [no important files changed] --- concepts/lists/about.md | 22 ++-- exercises/concept/card-games/.docs/hints.md | 21 ++- .../concept/card-games/.docs/introduction.md | 2 +- exercises/concept/card-games/lists_test.py | 124 +++++++++++------- 4 files changed, 101 insertions(+), 68 deletions(-) diff --git a/concepts/lists/about.md b/concepts/lists/about.md index 014a6d5672..851c10e138 100644 --- a/concepts/lists/about.md +++ b/concepts/lists/about.md @@ -18,7 +18,7 @@ Accessing elements, checking for membership via `in`, or appending items to the For a similar data structure that supports memory efficient `appends`/`pops` from both sides, see [`collections.deque`][deque], which has approximately the same O(1) performance in either direction. -Because lists are mutable and can contain references to arbitrary objects, they also take up more space in memory than a fixed-size [`array.array`][array.array] type of the same apparent length. +Because lists are mutable and can contain references to arbitrary Python objects, they also take up more space in memory than an [`array.array`][array.array] or a [`tuple`][tuple] (_which is immutable_) of the same apparent length. Despite this, lists are an extremely flexible and useful data structure and many built-in methods and operations in Python produce lists as their output. @@ -135,7 +135,8 @@ TypeError: 'int' object is not iterable ## Accessing elements -Items inside lists (_as well as elements in other sequence types such as [`str`][string] & [`tuple`][tuple]_), can be accessed using _bracket notation_. Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_). +Items inside lists (_as well as elements in other sequence types such as [`str`][string] & [`tuple`][tuple]_), can be accessed using _bracket notation_. +Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_). @@ -173,9 +174,11 @@ Items inside lists (_as well as elements in other sequence types such as [`str`] 'Toast' ``` -A section of a list can be accessed via _slice notation_ (`[start:stop]`). A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`. [_Slicing_][slice notation] returns a copy of the "sliced" items and does not modify the original `list`. +A section of a list can be accessed via _slice notation_ (`[start:stop]`). +A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`. +[_Slicing_][slice notation] returns a copy of the "sliced" items and does not modify the original `list`. -A `step` parameter can also be used in the slice (`[start:stop:step]`) to "skip over" or filter the returned elements (_for example, a `step` of 2 will select every other element in the section_): +A `step` parameter can also be used in the slice (`[::]`) to "skip over" or filter the returned elements (_for example, a `step` of 2 will select every other element in the section_): ```python >>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"] @@ -269,7 +272,7 @@ Lists can also be combined via various techniques: >>> first_one ['George', 5, 'cat', 'Tabby'] -# This loops through the first list and appends it's items to the end of the second list. +# This loops through the first list and appends its items to the end of the second list. >>> first_one = ["cat", "Tabby"] >>> second_one = ["George", 5] @@ -284,7 +287,7 @@ Lists can also be combined via various techniques: ## Some cautions Recall that variables in Python are _labels_ that point to _underlying objects_. -`lists` add one more layer as _container objects_ -- they hold object references for their collected items. +`lists` add one more layer as _container objects_ -- they hold object _references_ for their collected items. This can lead to multiple potential issues when working with lists, if not handled properly. @@ -305,21 +308,22 @@ A `shallow_copy` will create a new `list` object, but **will not** create new ob # Altering the list via the new name is the same as altering the list via the old name. >>> same_list.append("Clarke") ->>> same_list ["Tony", "Natasha", "Thor", "Bruce", "Clarke"] + >>> actual_names ["Tony", "Natasha", "Thor", "Bruce", "Clarke"] # Likewise, altering the data in the list via the original name will also alter the data under the new name. >>> actual_names[0] = "Wanda" ->>> same_list ['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke'] # If you copy the list, there will be two separate list objects which can be changed independently. >>> copied_list = actual_names.copy() >>> copied_list[0] = "Tony" + >>> actual_names ['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke'] + >>> copied_list ["Tony", "Natasha", "Thor", "Bruce", "Clarke"] ``` @@ -455,4 +459,4 @@ The collections module also provides a `UserList` type that can be customized to [set]: https://docs.python.org/3/library/stdtypes.html#set [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings [string]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str -[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple \ No newline at end of file +[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple diff --git a/exercises/concept/card-games/.docs/hints.md b/exercises/concept/card-games/.docs/hints.md index e293fffc92..56343b7b16 100644 --- a/exercises/concept/card-games/.docs/hints.md +++ b/exercises/concept/card-games/.docs/hints.md @@ -4,45 +4,44 @@ ## 1. Tracking Poker Rounds -- Lists in Python may be [constructed][constructed] in several ways. +- Lists in Python may be [constructed][constructed] in multiple ways. - This function should [return][return] a `list`. ## 2. Keeping all Rounds in the Same Place -- Sequence types such as `list` already support [common operations][common sequence operations]. +- Sequence types such as `list` support [common operations][common sequence operations]. - This function should [return][return] a `list`. ## 3. Finding Prior Rounds -- Sequence types such as `list` already support a few [common operations][common sequence operations]. +- Sequence types such as `list` support a few [common operations][common sequence operations]. - This function should [return][return] a `bool`. ## 4. Averaging Card Values -- To get the average, this function should count how many items are in the `list` and sum up their values. Then, return sum/count. +- To get the average, this function should count how many items are in the `list` and sum up their values. Then, return the sum divided by the count. ## 5. Alternate Averages -- Sequence types such as `list` already support a few [common operations][common sequence operations]. -- To access an element use the square brackets (`[]`) notation. -- Remember that the first element of the `list` is at index 0 from the left. -- In Python, negative indexing starts the count from the right-hand side. This mean that you can find the last element of a `list` at `index -1`. +- Sequence types such as `list` support a few [common operations][common sequence operations]. +- To access an element, use the square brackets (`[]`) notation. +- Remember that the first element of the `list` is at index 0 from the **left-hand** side. +- In Python, negative indexing starts at -1 from the **right-hand** side. This means that you can find the last element of a `list` by using `[-1]`. - Think about how you could reuse the code from the functions that you have already implemented. ## 6. More Averaging Techniques - Sequence types such as `list` already support a few [common operations][common sequence operations]. - Think about reusing the code from the functions that you just implemented. -- The slice syntax supports a step value. +- The slice syntax supports a _step value_ (`[::]`). ## 7. Bonus Round Rules -- Lists are mutable. Once a `list` is created, you can modify, delete or add any type of element you wish. +- Lists are _mutable_. Once a `list` is created, you can modify, delete or add any type of element you wish. - Python provides a wide range of [ways to modify `lists`][ways to modify `lists`]. [common sequence operations]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range [constructed]: https://docs.python.org/3/library/stdtypes.html#list -[iterate over a list in python]: https://www.geeksforgeeks.org/iterate-over-a-list-in-python/ [return]: https://www.w3schools.com/python/ref_keyword_return.asp [ways to modify `lists`]: https://realpython.com/python-lists-tuples/#lists-are-mutable diff --git a/exercises/concept/card-games/.docs/introduction.md b/exercises/concept/card-games/.docs/introduction.md index 1650246024..bb0c238117 100644 --- a/exercises/concept/card-games/.docs/introduction.md +++ b/exercises/concept/card-games/.docs/introduction.md @@ -2,7 +2,7 @@ A [`list`][list] is a mutable collection of items in _sequence_. Like most collections (_see the built-ins [`tuple`][tuple], [`dict`][dict] and [`set`][set]_), lists can hold reference to any (or multiple) data type(s) - including other lists. -Like any [sequence][sequence type], items can be accessed via `0-based index` number from the left and `-1-base index` from the right. +Like any [sequence][sequence type], items can be accessed via `0-based index` number from the left and `-1-based index` from the right. Lists can be copied in whole or in part via [slice notation][slice notation] or `.copy()`. Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`. diff --git a/exercises/concept/card-games/lists_test.py b/exercises/concept/card-games/lists_test.py index dd8ea2efc3..e55011294a 100644 --- a/exercises/concept/card-games/lists_test.py +++ b/exercises/concept/card-games/lists_test.py @@ -1,5 +1,6 @@ import unittest import pytest + from lists import ( get_rounds, concatenate_rounds, @@ -16,92 +17,121 @@ class CardGamesTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_get_rounds(self): - input_vars = [0, 1, 10, 27, 99, 666] + input_data = [0, 1, 10, 27, 99, 666] + result_data = [[0, 1, 2], [1, 2, 3], + [10, 11, 12], [27, 28, 29], + [99, 100, 101], [666, 667, 668]] - results = [[0, 1, 2], [1, 2, 3], - [10, 11, 12], [27, 28, 29], - [99, 100, 101], [666, 667, 668]] + for variant, (number, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', number=number, expected=expected): + actual_result = get_rounds(number) + error_message = (f'Called get_rounds({number}). ' + f'The function returned {actual_result}, ' + f'but the tests expected rounds {expected} ' + f'given the current round {number}.') - for variant, (number, rounds) in enumerate(zip(input_vars, results), start=1): - error_message = f'Expected rounds {rounds} given the current round {number}.' - with self.subTest(f'variation #{variant}', input=number, output=rounds): - self.assertEqual(rounds, get_rounds(number), msg=error_message) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=2) def test_concatenate_rounds(self): - input_vars = [([], []), ([0, 1], []), ([], [1, 2]), + input_data = [([], []), ([0, 1], []), ([], [1, 2]), ([1], [2]), ([27, 28, 29], [35, 36]), ([1, 2, 3], [4, 5, 6])] - results = [[], [0, 1], [1, 2], [1, 2], - [27, 28, 29, 35, 36], - [1, 2, 3, 4, 5, 6]] + result_data = [[], [0, 1], [1, 2], [1, 2], + [27, 28, 29, 35, 36], + [1, 2, 3, 4, 5, 6]] + + for variant, ((rounds_1, rounds_2), expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', rounds_1=rounds_1, rounds_2=rounds_2, expected=expected): + actual_result = concatenate_rounds(rounds_1, rounds_2) + error_message = (f'Called concatenate_rounds({rounds_1}, {rounds_2}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} as the concatenation ' + f'of {rounds_1} and {rounds_2}.') - for variant, ((rounds_1, rounds_2), rounds) in enumerate(zip(input_vars, results), start=1): - error_message = f'Expected {rounds} as the concatenation of {rounds_1} and {rounds_2}.' - with self.subTest(f'variation #{variant}', input=(rounds_1, rounds_2), output=rounds): - self.assertEqual(rounds, concatenate_rounds(rounds_1, rounds_2), msg=error_message) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_list_contains_round(self): - input_vars = [([], 1), ([1, 2, 3], 0), ([27, 28, 29, 35, 36], 30), - ([1], 1), ([1, 2, 3], 1), ([27, 28, 29, 35, 36], 29)] + input_data = [([], 1), ([1, 2, 3], 0), + ([27, 28, 29, 35, 36], 30), + ([1], 1), ([1, 2, 3], 1), + ([27, 28, 29, 35, 36], 29)] + result_data = [False, False, False, True, True, True] - results = [False, False, False, True, True, True] + for variant, ((rounds, round_number), expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', rounds=rounds, round_number=round_number, expected=expected): + actual_result = list_contains_round(rounds, round_number) + error_message = (f'Called list_contains_round({rounds}, {round_number}). ' + f'The function returned {actual_result}, but round {round_number} ' + f'{"is" if expected else "is not"} in {rounds}.') - for variant, ((rounds, round_number), contains) in enumerate(zip(input_vars, results), start=1): - error_message = f'Round {round_number} {"is" if contains else "is not"} in {rounds}.' - with self.subTest(f'variation #{variant}', input=(rounds, round_number), output=contains): - self.assertEqual(contains, list_contains_round(rounds, round_number), msg=error_message) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=4) def test_card_average(self): - input_vars = [[1], [5, 6, 7], [1, 2, 3, 4], [1, 10, 100]] + input_data = [[1], [5, 6, 7], [1, 2, 3, 4], [1, 10, 100]] + result_data = [1.0, 6.0, 2.5, 37.0] - results = [1.0, 6.0, 2.5, 37.0] + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = card_average(hand) + error_message = (f'Called card_average({hand}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} as the average of {hand}.') - for variant, (hand, average) in enumerate(zip(input_vars, results), start=1): - error_message = f'Expected {average} as the average of {hand}.' - with self.subTest(f'variation #{variant}', input=hand, output=average): - self.assertEqual(average, card_average(hand), msg=error_message) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=5) def test_approx_average_is_average(self): - input_vars = [[0, 1, 5], [3, 6, 9, 12, 150], [1, 2, 3, 5, 9], + input_data = [[0, 1, 5], [3, 6, 9, 12, 150], [1, 2, 3, 5, 9], [2, 3, 4, 7, 8], [1, 2, 3], [2, 3, 4], [2, 3, 4, 8, 8], [1, 2, 4, 5, 8]] - results = [False, False, False, False, True, True, True, True] + result_data = [False, False, False, False, True, True, True, True] + + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = approx_average_is_average(hand) + error_message = (f'Called approx_average_is_average({hand}). ' + f'The function returned {actual_result}, but ' + f'the hand {hand} {"does" if expected else "does not"} ' + f'yield the same approximate average.') - for variant, (hand, same) in enumerate(zip(input_vars, results), start=1): - error_message = f'Hand {hand} {"does" if same else "does not"} yield the same approximate average.' - with self.subTest(f'variation #{variant}', input=hand, output=same): - self.assertEqual(same, approx_average_is_average(hand), msg=error_message) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=6) def test_average_even_is_average_odd(self): - input_vars = [[5, 6, 8], [1, 2, 3, 4], [1, 2, 3], [5, 6, 7], [1, 3, 5, 7, 9]] + input_data = [[5, 6, 8], [1, 2, 3, 4], [1, 2, 3], [5, 6, 7], [1, 3, 5, 7, 9]] + result_data = [False, False, True, True, True] - results = [False, False, True, True, True] + for variant, (input_hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', input_hand=input_hand, expected=expected): + actual_result = average_even_is_average_odd(input_hand) + error_message = (f'Called average_even_is_average_odd({input_hand}). ' + f'The function returned {actual_result}, but ' + f'the hand {"does" if expected else "does not"} ' + f'yield the same odd-even average.') - for variant, (hand, same) in enumerate(zip(input_vars, results), start=1): - error_message = f'Hand {hand} {"does" if same else "does not"} yield the same odd-even average.' - with self.subTest(f'variation #{variant}', input=hand, output=same): - self.assertEqual(same, average_even_is_average_odd(hand), msg=error_message) + self.assertEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=7) def test_maybe_double_last(self): - input_vars = [[1, 2, 11], [5, 9, 11], [5, 9, 10], [1, 2, 3]] + input_data = [(1, 2, 11), (5, 9, 11), (5, 9, 10), (1, 2, 3), (1, 11, 8)] + result_data = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3], [1, 11, 8]] - results = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3]] + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=list(hand), expected=expected): + actual_result = maybe_double_last(list(hand)) + error_message = (f'Called maybe_double_last({list(hand)}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} as the maybe-doubled version of {list(hand)}.') - for variant, (hand, doubled_hand) in enumerate(zip(input_vars, results), start=1): - error_message = f'Expected {doubled_hand} as the maybe-doubled version of {hand}.' - with self.subTest(f'variation #{variant}', input=hand, output=doubled_hand): - self.assertEqual(doubled_hand, maybe_double_last(hand), msg=error_message) + self.assertEqual(actual_result, expected, msg=error_message) From b966f7932bbecfaa136ee2b8323b60fb7b478b8f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:06:52 -0800 Subject: [PATCH 550/826] Changed test error messages to sync with the runner changes. Other Msc. typo and language edits. (#3527) [no important files changed] --- concepts/basics/about.md | 17 +++++-- concepts/basics/links.json | 7 ++- .../guidos-gorgeous-lasagna/.docs/hints.md | 8 +-- .../.docs/introduction.md | 12 +++-- .../guidos-gorgeous-lasagna/lasagna_test.py | 49 ++++++++++++------- 5 files changed, 63 insertions(+), 30 deletions(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index 30ea083022..6a6fca716b 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -6,7 +6,8 @@ Imperative, declarative (e.g., functional), and object-oriented programming _sty Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. -Python was created by Guido van Rossum and first released in 1991. The [Python Software Foundation][psf] manages and directs resources for Python and CPython development and receives proposals for changes to the language from [members][psf membership] of the community via [Python Enhancement Proposals or PEPs][peps]. +Python was created by Guido van Rossum and first released in 1991. +The [Python Software Foundation][psf] manages and directs resources for Python and CPython development and receives proposals for changes to the language from [members][psf membership] of the community via [Python Enhancement Proposals or PEPs][peps]. Complete documentation for the current release can be found at [docs.python.org][python docs]. @@ -18,8 +19,14 @@ Complete documentation for the current release can be found at [docs.python.org] - [Python FAQs][python faqs] - [Python Glossary of Terms][python glossary of terms] +
+ +This first concept introduces 4 major Python language features: +1. Name Assignment (_variables and constants_), +2. Functions (_the `def` keyword and the `return` keyword_), +3. Comments, and +4. Docstrings. -This concept introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. ~~~~exercism/note @@ -32,9 +39,9 @@ On the Python track, [variables][variables] are always written in [`snake_case`] [snake case]: https://en.wikipedia.org/wiki/Snake_case +[the zen of python]: https://www.python.org/dev/peps/pep-0020/ [variables]: https://realpython.com/python-variables/ [what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.html -[the zen of python]: https://www.python.org/dev/peps/pep-0020/ ~~~~ @@ -127,8 +134,8 @@ def add_two_numbers(number_one, number_two): IndentationError: unindent does not match any outer indentation level ``` -Functions explicitly return a value or object via the [`return`][return] keyword. -Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none]. +Functions _explicitly_ return a value or object via the [`return`][return] keyword. +Functions that do not have an _explicit_ `return` expression will _implicitly_ return [`None`][none]. ```python # Function definition on first line. diff --git a/concepts/basics/links.json b/concepts/basics/links.json index 3e5561228e..1d1d640c9e 100644 --- a/concepts/basics/links.json +++ b/concepts/basics/links.json @@ -1,5 +1,6 @@ [ - {"url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/", + { + "url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/", "description": "Reuven Lerner: Understanding Python Assignment" }, { @@ -14,6 +15,10 @@ "url": "https://www.pythonmorsels.com/everything-is-an-object/", "description": "Python Morsels: Everything is an Object" }, + { + "url": "https://eli.thegreenplace.net/2012/03/23/python-internals-how-callables-work/", + "description": "Eli Bendersky: Python internals: how callables work" + }, { "url": "https://stackoverflow.com/questions/11328920/is-python-strongly-typed", "description": "dynamic typing and strong typing" diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md index bdab2e7eb3..9006524852 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md @@ -3,12 +3,14 @@ ## General - [The Python Tutorial][the python tutorial] can be a great introduction. -- [Numbers][numbers] in Python can be integers, floats, or complex. - [PEP 8][pep8] is the Python code style guide. +- [PEP 257][PEP257] details Python docstring conventions. +- [Numbers][numbers] in Python can be integers, floats, or complex. + ## 1. Define expected bake time in minutes -- You need to [name][naming] a constant, and [assign][assignment] it an integer value. +- You need to [name][naming] a constant, and [assign][assignment] it an [integer][numbers] value. ## 2. Calculate remaining bake time in minutes @@ -25,7 +27,7 @@ ## 4. Calculate total elapsed cooking time (prep + bake) in minutes -- You need to define a [function][defining-functions] with two parameters. +- You need to define a [function][defining functions] with two parameters. - Remember: you can always _call_ a function you've defined previously. - You can use the [mathematical operator for addition][python as a calculator] to sum values. - This function should [return a value][return]. diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index f9be66c3fe..6c8de4e69f 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -8,14 +8,18 @@ This includes numbers, strings, lists, and even functions. We'll dig more into what all of that means as we continue through the track. -This first exercise introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings. +This first exercise introduces 4 major Python language features: +1. Name Assignment (_variables and constants_), +2. Functions (_the `def` keyword and the `return` keyword_), +3. Comments, and +4. Docstrings. ~~~~exercism/note In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names. -On the Python track, [variables][variables] are always written in [`snake_case`][snake case], and constants in `SCREAMING_SNAKE_CASE` +On the Python track, [variables][variables] are always written in [`snake_case`][snake case], and constants in `SCREAMING_SNAKE_CASE`. [variables]: https://realpython.com/python-variables/ [snake case]: https://en.wikipedia.org/wiki/Snake_case @@ -84,7 +88,7 @@ IndentationError: unindent does not match any outer indentation level ``` Functions explicitly return a value or object via the [`return`][return] keyword. -Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none]. +Functions that do not have an _explicit_ `return` expression will _implicitly_ return [`None`][none]. ```python # Function definition on first line. @@ -201,7 +205,6 @@ Raise a number to an arbitrary power. Takes number_one and raises it to the power of number_two, returning the result. ``` -[pep257]: https://www.python.org/dev/peps/pep-0257/ [calls]: https://docs.python.org/3/reference/expressions.html#calls [comments]: https://realpython.com/python-comments-guide/#python-commenting-basics [docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings @@ -215,5 +218,6 @@ Raise a number to an arbitrary power. [module]: https://docs.python.org/3/tutorial/modules.html [none]: https://docs.python.org/3/library/constants.html [parameters]: https://docs.python.org/3/glossary.html#term-parameter +[pep257]: https://www.python.org/dev/peps/pep-0257/ [return]: https://docs.python.org/3/reference/simple_stmts.html#return [type hints]: https://docs.python.org/3/library/typing.html diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py index 7d0a7d9f1b..4066aa8a39 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py @@ -37,33 +37,45 @@ def test_EXPECTED_BAKE_TIME(self): @pytest.mark.task(taskno=2) def test_bake_time_remaining(self): input_data = [1, 2, 5, 10, 15, 23, 33, 39] - result_data = [40 - item for item in input_data] + result_data = [39, 38, 35, 30, 25, 17, 7, 1] - for variant, (time, result) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', time=time, result=result): - failure_msg = f'Expected: {result} but the bake time remaining was calculated incorrectly.' - self.assertEqual(bake_time_remaining(time), result, msg=failure_msg) + for variant, (time, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', time=time, expected=expected): + actual_result = bake_time_remaining(time) + failure_msg = (f'Called bake_time_remaining({time}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} as the remaining bake time.') + + self.assertEqual(actual_result, expected, msg=failure_msg) @pytest.mark.task(taskno=3) def test_preparation_time_in_minutes(self): input_data = [1, 2, 5, 8, 11, 15] - result_data = [item * 2 for item in input_data] + result_data = [2, 4, 10, 16, 22, 30] + + for variant, (layers, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', layers=layers, expected=expected): + actual_result = preparation_time_in_minutes(layers) + failure_msg = (f'Called preparation_time_in_minutes({layers}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} as the preparation time.') - for variant, (layers, time) in enumerate(zip(input_data, result_data), start=1): - with self.subTest(f'variation #{variant}', layers=layers, time=time): - failure_msg = f'Expected: {time} minutes, but preparation time was calculated incorrectly.' - self.assertEqual(preparation_time_in_minutes(layers), time, msg=failure_msg) + self.assertEqual(actual_result, expected, msg=failure_msg) @pytest.mark.task(taskno=4) def test_elapsed_time_in_minutes(self): layer_data = (1, 2, 5, 8, 11, 15) time_data = (3, 7, 8, 4, 15, 20) - result_data = [prep * 2 + elapsed for prep, elapsed in zip(layer_data, time_data)] + result_data = [5, 11, 18, 20, 37, 50] - for variant, (layers, time, total_time) in enumerate(zip(layer_data, time_data, result_data), start=1): - with self.subTest(f'variation #{variant}', layers=layers, time=time, total_time=total_time): - failure_msg = f'Expected {time} minutes elapsed, but the timing was calculated incorrectly.' - self.assertEqual(elapsed_time_in_minutes(layers, time), total_time, msg=failure_msg) + for variant, (layers, time, expected) in enumerate(zip(layer_data, time_data, result_data), start=1): + with self.subTest(f'variation #{variant}', layers=layers, time=time, expected=expected): + actual_result = elapsed_time_in_minutes(layers, time) + failure_msg = (f'Called elapsed_time_in_minutes({layers}, {time}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} as the elapsed time.') + + self.assertEqual(actual_result, expected, msg=failure_msg) @pytest.mark.task(taskno=5) def test_docstrings_were_written(self): @@ -77,6 +89,9 @@ def test_docstrings_were_written(self): for variant, function in enumerate(functions, start=1): with self.subTest(f'variation #{variant}', function=function): - failure_msg = f'Expected a docstring for `{function.__name__}`, but received `None` instead.' + actual_result = function.__doc__ + failure_msg = (f'Called {function.__name__}.__doc__. {actual_result} was returned, ' + f'but the tests expected a docstring for the {function.__name__} function.') + # Check that the __doc__ key is populated for the function. - self.assertIsNotNone(function.__doc__, msg=failure_msg) + self.assertIsNotNone(actual_result, msg=failure_msg) From 538e2d9729d41d7e8c2e6f117e4c4bf4283b1d41 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:07:12 -0800 Subject: [PATCH 551/826] Changed test error messages to sync with runner changes. Other misc. changes and typo corrections. (#3523) [no important files changed] --- concepts/comparisons/about.md | 11 +- concepts/comparisons/links.json | 24 ++-- .../concept/black-jack/.docs/introduction.md | 7 +- .../concept/black-jack/black_jack_test.py | 128 ++++++++++-------- 4 files changed, 90 insertions(+), 80 deletions(-) diff --git a/concepts/comparisons/about.md b/concepts/comparisons/about.md index c2f5faaad9..1d2c677d22 100644 --- a/concepts/comparisons/about.md +++ b/concepts/comparisons/about.md @@ -29,7 +29,7 @@ Numeric types are (mostly) an exception to this type matching rule. An `integer` **can** be considered equal to a `float` (_or an [`octal`][octal] equal to a [`hexadecimal`][hex]_), as long as the types can be implicitly converted for comparison. For the other numeric types ([complex][complex numbers], [decimal][decimal numbers], [fractions][rational numbers]), comparison operators are defined where they "make sense" (_where implicit conversion does not change the outcome_), but throw a `TypeError` if the underlying objects cannot be accurately converted for comparison. -For more information on the rules that python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation. +For more information on the rules that Python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation. ```python >>> import fractions @@ -47,7 +47,8 @@ True >>> 6/3 == 0b10 True -# An int can be converted to a complex number with a 0 imaginary part. +# An int can be converted to a complex +# number with a 0 imaginary part. >>> 17 == complex(17) True @@ -60,8 +61,8 @@ True ``` Any ordered comparison of a number to a `NaN` (_not a number_) type is `False`. -A confusing side-effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`. -If you are curious as to why `Nan` was defined this way in Python, this [Stack Overflow Post on NaN][so nan post] around the setting of the international standard is an interesting read. +A confusing side effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`. +If you are curious as to why `NaN` was defined this way in Python, this [Stack Overflow Post on NaN][so nan post] around the setting of the international standard is an interesting read. ```python >>> x = float('NaN') @@ -188,7 +189,7 @@ See the Python reference docs on [value comparisons][value comparisons none] and >>> my_fav_numbers is your_fav_numbers True -# The returned id will differ by system and python version. +# The returned id will differ by system and Python version. >>> id(my_fav_numbers) 4517478208 diff --git a/concepts/comparisons/links.json b/concepts/comparisons/links.json index ed61054b72..f16869e2b9 100644 --- a/concepts/comparisons/links.json +++ b/concepts/comparisons/links.json @@ -1,27 +1,19 @@ [ - { - "url": "https://docs.python.org/3/reference/expressions.html#comparisons", - "description": "Comparisons in Python (Python language reference)" - }, { "url": "https://www.tutorialspoint.com/python/python_basic_operators.htm", "description": "Python basic operators on Tutorials Point" }, { - "url": "https://data-flair.training/blogs/python-comparison-operators/", - "description": "Python comparison operators on Data Flair" - }, - { - "url": "https://www.python.org/dev/peps/pep-0207/", - "description": "PEP 207 to allow Operator Overloading for Comparison" + "url": "https://docs.python.org/3/reference/expressions.html#comparisons", + "description": "Comparisons in Python (Python language reference)" }, { "url": "https://docs.python.org/3/reference/expressions.html#is-not", "description": "Identity comparisons in Python (Python language reference)" }, { - "url": "https://docs.python.org/3/library/operator.html", - "description": "Operators (Python Docs)" + "url": "https://docs.python.org/3/reference/expressions.html#value-comparisons", + "description": "Value comparisons in Python (Python language reference)" }, { "url": "https://docs.python.org/3/library/stdtypes.html#typesnumeric", @@ -44,11 +36,11 @@ "description": "Python Object Model (Python docs)" }, { - "url": "https://docs.python.org/3/reference/datamodel.html#customization", - "description": "Basic Customization (Python language reference)" + "url": "https://www.python.org/dev/peps/pep-0207/", + "description": "PEP 207 to allow Operator Overloading for Comparison" }, { - "url": "https://docs.python.org/3/reference/expressions.html#value-comparisons", - "description": "Value comparisons in Python (Python language reference)" + "url": "https://docs.python.org/3/reference/datamodel.html#customization", + "description": "Basic Customization (Python language reference)" } ] diff --git a/exercises/concept/black-jack/.docs/introduction.md b/exercises/concept/black-jack/.docs/introduction.md index 207229359d..ec19d2f71f 100644 --- a/exercises/concept/black-jack/.docs/introduction.md +++ b/exercises/concept/black-jack/.docs/introduction.md @@ -59,7 +59,7 @@ True ``` Any ordered comparison of a number to a `NaN` (_not a number_) type is `False`. -A confusing side-effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`. +A confusing side effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`. ```python >>> x = float('NaN') @@ -186,7 +186,6 @@ The operators `in` and `not in` test for _membership_. For string and bytes types, ` in ` is `True` _**if and only if**_ `` is a substring of ``. ```python ->>> # A set of lucky numbers. >>> lucky_numbers = {11, 22, 33} >>> 22 in lucky_numbers @@ -196,7 +195,9 @@ True False # A dictionary of employee information. ->>> employee = {'name': 'John Doe', 'id': 67826, 'age': 33, 'title': 'ceo'} +>>> employee = {'name': 'John Doe', + 'id': 67826, 'age': 33, + 'title': 'ceo'} # Checking for the membership of certain keys. >>> 'age' in employee diff --git a/exercises/concept/black-jack/black_jack_test.py b/exercises/concept/black-jack/black_jack_test.py index 79072f95da..0962781f0a 100644 --- a/exercises/concept/black-jack/black_jack_test.py +++ b/exercises/concept/black-jack/black_jack_test.py @@ -15,84 +15,100 @@ class BlackJackTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_value_of_card(self): - data = [ - ('2', 2), ('5', 5), ('8', 8), - ('A', 1), ('10', 10), ('J', 10), - ('Q', 10), ('K', 10)] + test_data = [('2', 2), ('5', 5), ('8', 8), + ('A', 1), ('10', 10), ('J', 10), + ('Q', 10), ('K', 10)] - for variant, (card, value) in enumerate(data, 1): - with self.subTest(f'variation #{variant}', input=card, output=value): - error_msg = f'Expected {value} as the value of {card}.' + for variant, (card, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', card=card, expected=expected): + actual_result = value_of_card(card) + error_msg = (f'Called value_of_card({card}). ' + f'The function returned {actual_result} as the value of the {card} card, ' + f'but the test expected {expected} as the {card} card value.') + + self.assertEqual(actual_result, expected, msg=error_msg) - self.assertEqual(value_of_card(card), value, msg=error_msg) @pytest.mark.task(taskno=2) def test_higher_card(self): - data = [ - ('A', 'A', ('A', 'A')), - ('10', 'J', ('10', 'J')), - ('3', 'A', '3'), - ('3', '6', '6'), - ('Q', '10', ('Q', '10')), - ('4', '4', ('4', '4')), - ('9', '10', '10'), - ('6', '9', '9'), - ('4', '8', '8')] - - for variant, (card_one, card_two, result) in enumerate(data, 1): - with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, output=result): - error_msg = f'Expected {result} as the higher value of the cards {card_one, card_two}.' - - self.assertEqual(higher_card(card_one, card_two), result, msg=error_msg) + test_data = [('A', 'A', ('A', 'A')), + ('10', 'J', ('10', 'J')), + ('3', 'A', '3'), + ('3', '6', '6'), + ('Q', '10', ('Q', '10')), + ('4', '4', ('4', '4')), + ('9', '10', '10'), + ('6', '9', '9'), + ('4', '8', '8')] + + for variant, (card_one, card_two, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, expected=expected): + actual_result = higher_card(card_one, card_two) + error_msg = (f'Called higher_card({card_one}, {card_two}). ' + f'The function returned {actual_result}, ' + f'but the test expected {expected} as the result for the cards {card_one, card_two}.') + + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=3) def test_value_of_ace(self): - data = [ - ('2', '3', 11), ('3', '6', 11), ('5', '2', 11), - ('8', '2', 11), ('5', '5', 11), ('Q', 'A', 1), - ('10', '2', 1), ('7', '8', 1), ('J', '9', 1), - ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1)] + test_data = [('2', '3', 11), ('3', '6', 11), ('5', '2', 11), + ('8', '2', 11), ('5', '5', 11), ('Q', 'A', 1), + ('10', '2', 1), ('7', '8', 1), ('J', '9', 1), + ('K', 'K', 1), ('2', 'A', 1), ('A', '2', 1)] - for variant, (card_one, card_two, ace_value) in enumerate(data, 1): + for variant, (card_one, card_two, ace_value) in enumerate(test_data, 1): with self.subTest(f'variation #{variant}', card_one=card_one, card_two=card_two, ace_value=ace_value): - error_msg = f'Expected {ace_value} as the value of an ace card when the hand has {card_one, card_two}.' + actual_result = value_of_ace(card_one, card_two) + error_msg = (f'Called value_of_ace({card_one}, {card_two}). ' + f'The function returned {actual_result}, ' + f'but the test expected {ace_value} as the value of an ace card ' + f'when the hand includes {card_one, card_two}.') self.assertEqual(value_of_ace(card_one, card_two), ace_value, msg=error_msg) @pytest.mark.task(taskno=4) def test_is_blackjack(self): - data = [ - (('A', 'K'), True), (('10', 'A'), True), - (('10', '9'), False), (('A', 'A'), False), - (('4', '7'), False), (('9', '2'), False), - (('Q', 'K'), False)] + test_data = [(('A', 'K'), True), (('10', 'A'), True), + (('10', '9'), False), (('A', 'A'), False), + (('4', '7'), False), (('9', '2'), False), + (('Q', 'K'), False)] - for variant, (hand, blackjack) in enumerate(data, 1): - with self.subTest(f'variation #{variant}', input=hand, output=blackjack): - error_msg = f'Hand {hand} {"is" if blackjack else "is not"} a blackjack.' + for variant, (hand, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = is_blackjack(*hand) + error_msg = (f'Called is_blackjack({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"is" if expected else "is not"} a blackjack.') - self.assertEqual(is_blackjack(*hand), blackjack, msg=error_msg) + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=5) def test_can_split_pairs(self): - data = [ - (('Q', 'K'), True), (('6', '6'), True), (('A', 'A'), True), - (('10', 'A'), False), (('10', '9'), False)] + test_data = [(('Q', 'K'), True), (('6', '6'), True), + (('A', 'A'), True),(('10', 'A'), False), + (('10', '9'), False)] - for variant, (hand, split_pairs) in enumerate(data, 1): - with self.subTest(f'variation #{variant}', input=hand, output=split_pairs): - error_msg = f'Hand {hand} {"can" if split_pairs else "cannot"} be split into pairs.' + for variant, (hand, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', input=hand, expected=expected): + actual_result = can_split_pairs(*hand) + error_msg = (f'Called can_split_pairs({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"can" if expected else "cannot"} be split into pairs.') - self.assertEqual(can_split_pairs(*hand), split_pairs, msg=error_msg) + self.assertEqual(actual_result, expected, msg=error_msg) @pytest.mark.task(taskno=6) def test_can_double_down(self): - data = [ - (('A', '9'), True), (('K', 'A'), True), (('4', '5'), True), - (('A', 'A'), False), (('10', '2'), False), (('10', '9'), False)] - - for variant, (hand, double_down) in enumerate(data, 1): - with self.subTest(f'variation #{variant}', input=hand, output=double_down): - error_msg = f'Hand {hand} {"can" if double_down else "cannot"} be doubled down.' - - self.assertEqual(can_double_down(*hand), double_down, msg=error_msg) + test_data = [(('A', '9'), True), (('K', 'A'), True), + (('4', '5'), True),(('A', 'A'), False), + (('10', '2'), False), (('10', '9'), False)] + + for variant, (hand, expected) in enumerate(test_data, 1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = can_double_down(*hand) + error_msg = (f'Called can_double_down({hand[0]}, {hand[1]}). ' + f'The function returned {actual_result}, ' + f'but hand {hand} {"can" if expected else "cannot"} be doubled down.') + + self.assertEqual(actual_result, expected, msg=error_msg) From 89db59b92bbc4e6f098ddf7f5794d5eede71a3e5 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 6 Nov 2023 22:07:37 -0800 Subject: [PATCH 552/826] [Meltdown Mitigation]: Modified Test Error Messages & Touched Up Docs (#3515) * Modified test error messages to better match test runner changes. Also did touchup for links in docs. * Updated test error messages to be more verbose. * Revert "Updated test error messages to be more verbose." This reverts commit e800f4ec6db613b21f5d157de7578f0fe8189469. [no important files changed] --- .../concept/meltdown-mitigation/.docs/hints.md | 16 ++++++++-------- .../meltdown-mitigation/.docs/introduction.md | 10 +++++----- .../meltdown-mitigation/.meta/design.md | 4 ++-- .../meltdown-mitigation/conditionals_test.py | 18 ++++++++++++------ 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/exercises/concept/meltdown-mitigation/.docs/hints.md b/exercises/concept/meltdown-mitigation/.docs/hints.md index 510b8bd4fb..e500bb4dcd 100644 --- a/exercises/concept/meltdown-mitigation/.docs/hints.md +++ b/exercises/concept/meltdown-mitigation/.docs/hints.md @@ -8,7 +8,7 @@ ## 1. Check for criticality -- Comparison operators and boolean operations can be combined and used with conditionals. +- Comparison operators ([comparisons][comparisons review]) and boolean operations ([concept:python/bools]()) can be combined and used with conditionals. - Conditional expressions must evaluate to `True` or `False`. - `else` can be used for a code block that will execute when all conditional tests return `False`. @@ -16,9 +16,9 @@ >>> item = 'blue' >>> item_2 = 'green' - >>> if len(item) >=3 and len(item_2) < 5: + >>> if len(item) >= 3 and len(item_2) < 5: print('Both pass the test!') - elif len(item) >=3 or len(item_2) < 5: + elif len(item) >= 3 or len(item_2) < 5: print('One passes the test!') else: print('None pass the test!') @@ -29,20 +29,20 @@ ## 2. Determine the Power output range - Comparison operators can be combined and used with conditionals. -- Any number of `elif` statements can be used as "branches". +- Any number of `elif` statements can be used as decision "branches". - Each "branch" can have a separate `return` ## 3. Fail Safe Mechanism - Comparison operators can be combined and used with conditionals. -- Any number of `elif` statements can be used as "branches". +- Any number of `elif` statements can be used as decision "branches". - Each "branch" can have a separate `return` -[python comparisons examples]: https://www.tutorialspoint.com/python/comparison_operators_example.htm [boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not +[comparisons review]: https://www.learnpython.dev/02-introduction-to-python/090-boolean-logic/20-comparisons/ [comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons +[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html [python booleans]: https://realpython.com/python-boolean/ +[python comparisons examples]: https://www.tutorialspoint.com/python/comparison_operators_example.htm [real python conditionals]: https://realpython.com/python-conditional-statements/ -[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html - diff --git a/exercises/concept/meltdown-mitigation/.docs/introduction.md b/exercises/concept/meltdown-mitigation/.docs/introduction.md index da5ebbae19..fe9fff4d5d 100644 --- a/exercises/concept/meltdown-mitigation/.docs/introduction.md +++ b/exercises/concept/meltdown-mitigation/.docs/introduction.md @@ -3,9 +3,9 @@ In Python, [`if`][if statement], `elif` (_a contraction of 'else and if'_) and `else` statements are used to [control the flow][control flow tools] of execution and make decisions in a program. Unlike many other programming languages, Python versions 3.9 and below do not offer a formal case-switch statement, instead using multiple `elif` statements to serve a similar purpose. -Python 3.10 introduces a variant case-switch statement called `pattern matching`, which will be covered separately in another concept. +Python 3.10 introduces a variant case-switch statement called `structural pattern matching`, which will be covered separately in another concept. -Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` directly, or by evaluating ["truthy" or "falsy"][truth value testing]. +Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` type directly, or by evaluating as ["truthy" or "falsy"][truth value testing]. ```python x = 10 @@ -74,8 +74,8 @@ else: '13' ``` -[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement -[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools -[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing [boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not [comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons +[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools +[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/concept/meltdown-mitigation/.meta/design.md b/exercises/concept/meltdown-mitigation/.meta/design.md index cdc482c6d0..33ef7e3b95 100644 --- a/exercises/concept/meltdown-mitigation/.meta/design.md +++ b/exercises/concept/meltdown-mitigation/.meta/design.md @@ -3,11 +3,11 @@ ## Goal -The goal of this exercise is to teach the student what is a `conditional` and how they are used in Python. +The goal of this exercise is to teach the student about `conditionals` and how they are used in Python. ## Learning objectives -- learn some general things about `control flow` in python +- learn some general things about `control flow` in Python - create a `conditional` structure to choose something, take a decision - use an `if...else` structure - use an `if..elif...else` structure diff --git a/exercises/concept/meltdown-mitigation/conditionals_test.py b/exercises/concept/meltdown-mitigation/conditionals_test.py index 9837c34302..5e48ca326c 100644 --- a/exercises/concept/meltdown-mitigation/conditionals_test.py +++ b/exercises/concept/meltdown-mitigation/conditionals_test.py @@ -30,8 +30,10 @@ def test_is_criticality_balanced(self): # pylint: disable=assignment-from-no-return actual_result = is_criticality_balanced(temp, neutrons_emitted) - failure_message = (f'Expected {expected} but returned {actual_result} ' - f'with T={temp} and neutrons={neutrons_emitted}') + failure_message = (f'Called is_criticality_balanced({temp}, {neutrons_emitted}). ' + f' The function returned {actual_result}, ' + f'but the test expected {expected} as the return value.') + self.assertEqual(actual_result, expected, failure_message) @pytest.mark.task(taskno=2) @@ -52,8 +54,10 @@ def test_reactor_efficiency(self): # pylint: disable=assignment-from-no-return actual_result = reactor_efficiency(voltage, current, theoretical_max_power) - failure_message = (f'Expected {expected} but returned {actual_result} ' - f'with voltage={voltage}, current={current}, max_pow={theoretical_max_power}') + failure_message =(f'Called reactor_efficiency({voltage}, {current}, {theoretical_max_power}). ' + f'The function returned {actual_result}, ' + f'but the test expected {expected} as the return value.') + self.assertEqual(actual_result, expected, failure_message) @pytest.mark.task(taskno=3) @@ -71,6 +75,8 @@ def test_fail_safe(self): # pylint: disable=assignment-from-no-return actual_result = fail_safe(temp, neutrons_per_second, threshold) - failure_message = (f'Expected {expected} but returned {actual_result} with T={temp}, ' - f'neutrons={neutrons_per_second}, threshold={threshold}') + failure_message = (f'Called fail_safe({temp}, {neutrons_per_second}, {threshold}). ' + f'The function returned {actual_result}, ' + f'but the test expected {expected} as the return value.') + self.assertEqual(actual_result, expected, failure_message) From 42dcefbf7e9109988676b67da00766ab72f97f6f Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 9 Nov 2023 21:35:02 +0100 Subject: [PATCH 553/826] Pin GitHub Actions runners to a specific version (#3547) --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/pr-commenter.yml | 2 +- .github/workflows/stale.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 8a8803bfb9..5d1acd2ebe 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -12,7 +12,7 @@ on: jobs: housekeeping: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -49,7 +49,7 @@ jobs: ./bin/template_status.py -v -p .problem-specifications canonical_sync: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: housekeeping strategy: matrix: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index dfd6c59c51..b547987dc2 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -5,7 +5,7 @@ on: jobs: comment-on-new-issue: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 name: Comments for every NEW issue. steps: - name: Checkout diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index d8cc693906..411f9f198d 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -4,7 +4,7 @@ on: jobs: pr-comment: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: exercism/pr-commenter-action@v1.5.1 with: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 41156435d8..e43617427f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,7 +6,7 @@ on: jobs: stale: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/stale@v8 with: diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index b3668593b8..9db386422d 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -8,7 +8,7 @@ on: jobs: test-runner: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Run test-runner From e3a189c5194a4b3c06422575a22fa45c02e2817d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 14 Nov 2023 15:11:36 +0100 Subject: [PATCH 554/826] Pin GitHub Actions workflows to a specific version (#3548) --- .github/workflows/ci-workflow.yml | 8 ++++---- .github/workflows/issue-commenter.yml | 6 +++--- .github/workflows/pr-commenter.yml | 2 +- .github/workflows/stale.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 5d1acd2ebe..1632f55e2e 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,10 +14,10 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 with: python-version: 3.11.2 @@ -55,9 +55,9 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index b547987dc2..cbe6388366 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,16 +9,16 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Read issue-comment.md id: issue-comment - uses: juliangruber/read-file-action@v1 + uses: juliangruber/read-file-action@02bbba9876a8f870efd4ad64e3b9088d3fb94d4b with: path: .github/issue-comment.md - name: Base comment - uses: jd-0001/gh-action-comment-on-new-issue@v2.0.3 + uses: jd-0001/gh-action-comment-on-new-issue@c443e1151cc69b146fd6918cc983ec1bd27ab254 with: message: "${{ steps.issue-comment.outputs.content }}" ignore-label: ":anger: prickle" diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index 411f9f198d..3b2592cda1 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -6,7 +6,7 @@ jobs: pr-comment: runs-on: ubuntu-22.04 steps: - - uses: exercism/pr-commenter-action@v1.5.1 + - uses: exercism/pr-commenter-action@085ef62d2a541a112c3ade1d24deea83665ea186 with: github-token: "${{ github.token }}" config-file: ".github/pr-commenter.yml" \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index e43617427f..86f881a21c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-22.04 steps: - - uses: actions/stale@v8 + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 9db386422d..1228b8f104 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Run test-runner run: docker-compose run test-runner From 1cd88ed899cc0416c422cf24be8b4e075f935864 Mon Sep 17 00:00:00 2001 From: Laran Evans Date: Thu, 23 Nov 2023 05:49:45 -0800 Subject: [PATCH 555/826] Update about.md (#3552) Spelling --- concepts/conditionals/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index 3b3d5b1ba1..8f0ae8fde0 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -55,7 +55,7 @@ else: >>> z is greater than x and y ``` -[Boolen operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: +[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: ```python From 6a6eacf391c402c394e49d063ebae3d81b3800d6 Mon Sep 17 00:00:00 2001 From: JoPro0 <113715866+JoPro0@users.noreply.github.com> Date: Mon, 4 Dec 2023 21:36:45 +0100 Subject: [PATCH 556/826] added missing greater-than sign in about.md (#3557) --- concepts/lists/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/lists/about.md b/concepts/lists/about.md index 851c10e138..657cd43ff6 100644 --- a/concepts/lists/about.md +++ b/concepts/lists/about.md @@ -7,7 +7,7 @@ A [`list`][list] is a mutable collection of items in _sequence_. Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`. - List elements can be iterated over using the `for item in ` construct. `for index, item in enumerate(` construct. `for index, item in enumerate()` can be used when both the element index and the element value are needed. Lists are implemented as [dynamic arrays][dynamic array] -- similar to Java's [`Arraylist`][arraylist] type, and are most often used to store groups of similar data (_strings, numbers, sets etc._) of unknown length (_the number of entries may arbitrarily expand or shrink_). From fea327ba0913dba6eb9196879e4611284ddd3115 Mon Sep 17 00:00:00 2001 From: colinleach Date: Tue, 5 Dec 2023 14:51:17 -0700 Subject: [PATCH 557/826] complex-numbers concept docs (#3554) * complex-numbers concept docs * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * fixed several links * some relatively small fixes, mainly typos and formatting * Moved sections around, in the search for a clearer flow of ideas. * added bit on `complex(string)` constructor. * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Edited About to serve as Introduction * Titled Introduction as Introduction --------- Co-authored-by: BethanyG --- concepts/complex-numbers/.meta/config.json | 4 +- concepts/complex-numbers/about.md | 260 ++++++++++++++++++++- concepts/complex-numbers/introduction.md | 93 +++++++- concepts/complex-numbers/links.json | 16 +- 4 files changed, 361 insertions(+), 12 deletions(-) diff --git a/concepts/complex-numbers/.meta/config.json b/concepts/complex-numbers/.meta/config.json index 9b9e8da5a9..ca6ccc8811 100644 --- a/concepts/complex-numbers/.meta/config.json +++ b/concepts/complex-numbers/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "Complex numbers are a fundamental data type in Python, along with int and float. Further support is added with the cmath module, which is part of the Python standard library.", + "authors": ["BethanyG", "colinleach"], "contributors": [] } diff --git a/concepts/complex-numbers/about.md b/concepts/complex-numbers/about.md index c628150d56..dfe067be4e 100644 --- a/concepts/complex-numbers/about.md +++ b/concepts/complex-numbers/about.md @@ -1,2 +1,260 @@ -#TODO: Add about for this concept. +# About + +`Complex numbers` are not complicated. +They just need a less alarming name. + +They are so useful, especially in engineering and science, that Python includes [`complex`][complex] as a standard numeric type alongside integers ([`int`s][ints]) and floating-point numbers ([`float`s][floats]). + + +## Basics + +A `complex` value in Python is essentially a pair of floating-point numbers. +These are called the "real" and "imaginary" parts, for unfortunate historical reasons. +Again, it is best to focus on the underlying simplicity and not the strange names. + +There are two common ways to create complex numbers. + +1) The [`complex(real, imag)`][complex] constructor takes two `float` parameters: + +```python +>>> z1 = complex(1.5, 2.0) +>>> z1 +(1.5+2j) +``` + +The constructor can also parse string input. +This has the odd limitation that it fails if the string contains spaces. + +```python +>>> complex('4+2j') +(4+2j) + +>>> complex('4 + 2j') +Traceback (most recent call last): + File "", line 1, in +ValueError: complex() arg is a malformed string +``` + + +2) The complex number can be specified as ` + j` literal, or just `j` if the real part is zero: + + +```python +>>> z2 = 2.0 + 1.5j +>>> z2 +(2+1.5j) +``` +The end result is identical to using the `complex()` constructor. + + +There are two rules for that imaginary part of the complex number: + + +- It is designated with `j` (not `i` as you may see in math textbooks). + +- The `j` must immediately follow a number, to prevent Python seeing it as a variable name. If necessary, use `1j`. + +```python +>>> j +Traceback (most recent call last): + File "", line 1, in +NameError: name 'j' is not defined + +>>> 1j +1j + +>>> type(1j) + +``` + +Most engineers are happy with `j`. +Most scientists and mathematicians prefer the mathematical notation `i` for _imaginary_, but that notation conflicts with the use of `i` to mean _current_ in Electrical Engineering. +So in designing Python, the Electrical Engineers won. + + +To access the parts of a complex number individually: + +```python +>>> z2.real +2.0 +>>> z2.imag +1.5 +``` + +Either part can be zero and mathematicians may then talk of the number being "wholly real" or "wholly imaginary". +However, it is still a complex number in Python: + + +```python +>>> complex(0, 1) +1j +>>> type(complex(0, 1)) + + +>>> complex(1, 0) +(1+0j) +``` + +You may have heard that "`i` (or `j`) is the square root of -1". + +For now, all this means is that the imaginary part _by definition_ satisfies the equality +```python +1j * 1j == -1 # => True +``` + +This is a simple idea, but it leads to interesting consequences. + +## Arithmetic + +Most of the [`operators`][operators] used with floats and ints also work with complex numbers: + + +```python +>>> z1 = (1.5+2j) +>>> z2 = (2+1.5j) + + +>>> z1 + z2 # addition +(3.5+3.5j) + +>>> z1 - z2 # subtraction +(-0.5+0.5j) + +>>> z1 * z2 # multiplication +6.25j + +>>> z1 / z2 # division +(0.96+0.28j) + +>>> z1 ** 2 # exponentiation +(-1.75+6j) + +>>> 2 ** z1 # another exponentiation +(0.5188946835878313+2.7804223253571183j) + +>>> 1j ** 2 # j * j == -1 +(-1+0j) +``` + +Explaining the rules for complex number multiplication and division is out of scope for this concept (_and you are unlikely to have to perform those operations "by hand" very often_). + +Any [mathematical][math-complex] or [electrical engineering][engineering-complex] introduction to complex numbers will cover this, should you want to dig into the topic. + +Alternatively, Exercism has a `Complex Numbers` practice exercise where you can implement a complex number class with these operations from first principles. + + +Integer division is ___not___ possible on complex numbers, so the `//` and `%` operators and `divmod()` functions will fail for the complex number type. + + +There are two functions implemented for numeric types that are very useful when working with complex numbers: + +- `.conjugate()` simply flips the sign of the imaginary part of a complex number (_from + to - or vice-versa_). + - Because of the way complex multiplication works, this is more useful than you might think. +- `abs()` is guaranteed to return a real number with no imaginary part. + + +```python +>>> z1 +(1.5+2j) + +>>> z1.conjugate() # flip the z1.imag sign +(1.5-2j) + +>>> abs(z1) # sqrt(z1.real ** 2 + z1.imag ** 2) +2.5 +``` + +## The `cmath` module + +The Python standard library has a [`math`][math-module] module full of useful functionality for working with real numbers. + +It also has an equivalent [`cmath`][cmath] module for working with complex numbers. + + +We encourage you to read through the module and experiment, but the main categories are: + +- Conversion between Cartesian and polar coordinates, +- Exponential and log functions, +- Trigonometric functions, +- Hyperbolic functions, +- Classification functions, and +- Useful constants. + +Here is an example using some constants: + +```python +>>> import cmath + +>>> euler = cmath.exp(1j * cmath.pi) # Euler's equation + +>>> euler.real +-1.0 +>>> round(euler.imag, 15) # round to 15 decimal places +0.0 +``` + +So a simple expression with three of the most important constants in nature `e`, `i` (or `j`) and `pi` gives the result `-1`. +Some people believe this is the most beautiful result in all of mathematics. +It dates back to around 1740. + +----- + +## Optional section: a Complex Numbers FAQ + +This part can be skipped, unless you are interested. + +### Isn't this some strange new piece of pure mathematics? + +It was strange and new in the 16th century. + +500 years later, it is central to most of engineering and the physical sciences. + +### Why would anyone use these? + +It turns out that complex numbers are the simplest way to describe anything that rotates or anything with a wave-like property. +So they are used widely in electrical engineering, audio processing, physics, computer gaming, and navigation - to name only a few applications. + +You can see things rotate. +Complex numbers may not make the world go round, but they are great for explaining _what happens_ as a result of the world going round: look at any satellite image of a major storm. + + +Less obviously, sound is wave-like, light is wave-like, radio signals are wave-like, and even the economy of your home country is at least partly wave-like. + + +A lot of this wave processing can be done with trig functions (`sin()` and `cos()`) but that gets messy quite quickly. + +Complex exponentials are ___much___ easier to work with. + +### But I don't need complex numbers! + + +Only true if you are living in a cave and foraging for your food. + +If you are reading this on any sort of screen, you are utterly dependent on some useful 20th-Century advances made through the use of complex numbers. + + +1. __Semiconductor chips__. + - These make no sense in classical physics and can only be explained (and designed) by quantum mechanics (QM). + - In QM, everything is complex-valued by definition. (_its waveforms all the way down_) + +2. __The Fast Fourier Transform algorithm__. + - FFT is an application of complex numbers, and it is in _everything_ connected to sound transmission, audio processing, photos, and video. + + -MP3 and other audio formats use FFT for compression, ensuring more audio can fit within a smaller storage space. + - JPEG compression and MP4 video, among many other image and video formats also use FTT for compression. + + - FFT is also deployed in the digital filters that allow cellphone towers to separate your personal cell signal from everyone else's. + + +So, you are probably using technology that relies on complex number calculations thousands of times per second. + + +[complex]: https://docs.python.org/3/library/functions.html#complex +[cmath]: https://docs.python.org/3/library/cmath.html +[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[math-module]: https://docs.python.org/3/library/math.html +[math-complex]: https://www.nagwa.com/en/videos/143121736364/ +[engineering-complex]: https://www.khanacademy.org/science/electrical-engineering/ee-circuit-analysis-topic/ee-ac-analysis/v/ee-complex-numbers +[ints]: https://docs.python.org/3/library/functions.html#int +[floats]: https://docs.python.org/3/library/functions.html#float diff --git a/concepts/complex-numbers/introduction.md b/concepts/complex-numbers/introduction.md index fcde74642c..a82f47cb6c 100644 --- a/concepts/complex-numbers/introduction.md +++ b/concepts/complex-numbers/introduction.md @@ -1,2 +1,93 @@ -#TODO: Add introduction for this concept. +# Introduction +`Complex numbers` are not complicated. +They just need a less alarming name. + +They are so useful, especially in engineering and science (_everything from JPEG compression to quantum mechanics_), that Python includes [`complex`][complex] as a standard numeric type alongside integers ([`int`s][ints]) and floating-point numbers ([`float`s][floats]). + +A `complex` value in Python is essentially a pair of floating-point numbers: + +```python +>>> my_complex = 5.443+6.77j +(5.443+6.77j) +``` + +These are called the "real" and "imaginary" parts. +You may have heard that "`i` (or `j`) is the square root of -1". +For now, all this means is that the imaginary part _by definition_ satisfies the equality `1j * 1j == -1`. +This is a simple idea, but it leads to interesting mathematical consequences. + +In Python, the "imaginary" part is designated with `j` (_not `i` as you would see in math textbooks_), and +the `j` must immediately follow a number, to prevent Python seeing it as a variable name: + + +```python +>>> j +Traceback (most recent call last): + File "", line 1, in +NameError: name 'j' is not defined + +>>> 1j +1j + +>>> type(1j) + +``` + + +There are two common ways to create complex numbers. + +1) The [`complex(real, imag)`][complex] constructor takes two `float` parameters: + + ```python + >>> z1 = complex(1.5, 2.0) + >>> z1 + (1.5+2j) + ``` + + The constructor can also parse string input. + This has the odd limitation that it fails if the string contains spaces. + + ```python + >>> complex('4+2j') + (4+2j) + + >>> complex('4 + 2j') + Traceback (most recent call last): + File "", line 1, in + ValueError: complex() arg is a malformed string + ``` + + +2) The complex number can be specified as ` + j` literal, or just `j` if the real part is zero: + + + ```python + >>> z2 = 2.0 + 1.5j + >>> z2 + (2+1.5j) + ``` + The end result is identical to using the `complex()` constructor. + + +## Arithmetic + +Most of the [`operators`][operators] used with floats and ints also work with complex numbers. + +Integer division is _**not**_ possible on complex numbers, so the `//` and `%` operators and `divmod()` functions will fail for the complex number type. + +Explaining the rules for complex number multiplication and division is out of scope for this concept (_and you are unlikely to have to perform those operations "by hand" very often_). + +Any [mathematical][math-complex] or [electrical engineering][engineering-complex] introduction to complex numbers will cover these scenarios, should you want to dig into the topic. + +The Python standard library has a [`math`][math-module] module full of useful functionality for working with real numbers and the [`cmath`][cmath] module is its equivalent for working with complex numbers. + + +[cmath]: https://docs.python.org/3/library/cmath.html +[complex]: https://docs.python.org/3/library/functions.html#complex +[engineering-complex]: https://www.khanacademy.org/science/electrical-engineering/ee-circuit-analysis-topic/ee-ac-analysis/v/ee-complex-numbers +[floats]: https://docs.python.org/3/library/functions.html#float +[ints]: https://docs.python.org/3/library/functions.html#int +[math-complex]: https://www.nagwa.com/en/videos/143121736364/ +[math-module]: https://docs.python.org/3/library/math.html +[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex diff --git a/concepts/complex-numbers/links.json b/concepts/complex-numbers/links.json index eb5fb7c38a..759ef1689f 100644 --- a/concepts/complex-numbers/links.json +++ b/concepts/complex-numbers/links.json @@ -1,18 +1,18 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex/", + "description": "Operations on numeric types." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/functions.html#complex/", + "description": "The complex class." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/cmath.html/", + "description": "Module documentation for cmath." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/math.html/", + "description": "Module documentation for math." } ] From 44b0c7f41ad7760d00c3e166bb1a9a81bcc882d4 Mon Sep 17 00:00:00 2001 From: colinleach Date: Tue, 5 Dec 2023 14:52:54 -0700 Subject: [PATCH 558/826] fractions concept (#3555) * fractions concept * added links, tried to clarify wording * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Introduction from About Added some links and edited config.json. --------- Co-authored-by: BethanyG --- concepts/fractions/.meta/config.json | 5 ++ concepts/fractions/about.md | 122 +++++++++++++++++++++++++++ concepts/fractions/introduction.md | 85 +++++++++++++++++++ concepts/fractions/links.json | 18 ++++ config.json | 5 ++ 5 files changed, 235 insertions(+) create mode 100644 concepts/fractions/.meta/config.json create mode 100644 concepts/fractions/about.md create mode 100644 concepts/fractions/introduction.md create mode 100644 concepts/fractions/links.json diff --git a/concepts/fractions/.meta/config.json b/concepts/fractions/.meta/config.json new file mode 100644 index 0000000000..621a3766d8 --- /dev/null +++ b/concepts/fractions/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "The fractions module enables working with rational numbers, which preserve exact values and avoid the rounding errors common with floats.", + "authors": ["BethanyG", "colinleach"], + "contributors": [] +} diff --git a/concepts/fractions/about.md b/concepts/fractions/about.md new file mode 100644 index 0000000000..d41124c39c --- /dev/null +++ b/concepts/fractions/about.md @@ -0,0 +1,122 @@ +# About + +The [`Fractions`][fractions] module allows us to create and work with [`rational numbers`][rational]: fractions with an integer numerator divided by an integer denominator. + +For example, we can store `2/3` as an exact fraction instead of the approximate `float` value `0.6666...` + +## Creating Fractions + + +Unlike `int`, `float`, and `complex` numbers, fractions do not have a literal form. +However, the fractions constructor is quite flexible. + +Most obviously, it can take take two integers. +Common factors are automatically removed, converting the fraction to its "lowest form": the smallest integers that accurately represent the fraction. + + +```python +>>> from fractions import Fraction + +>>> f1 = Fraction(2, 3) # 2/3 +>>> f1 +Fraction(2, 3) + +>>> f2 = Fraction(6, 9) +>>> f2 +Fraction(2, 3) # automatically simplified + +>>> f1 == f2 +True +``` + +The fractions constructor can also parse a string representation: + + +```python +>>> f3 = Fraction('2/3') +>>> f3 +Fraction(2, 3) +``` + +It can also work with `float` parameters, but this may run into problems with the approximate nature of representing the decimal value internally as binary. +For more on this representation issue, see the [0.30000000000000004][0.30000000000000004] website, and [Floating Point Arithmetic: Issues and Limitations ][fp-issues] in the Python documentation. + +For a more reliable result when using floats with fractions, there is the `.limit_denominator()` method. + + +[`.limit_denominator()`][limit_denominator] can take an integer parameter if you have specific requirements, but even the default (`max_denominator=1000000`) can work well and give an acceptable, simple approximation. + +```python +>>> Fraction(1.2) +Fraction(5404319552844595, 4503599627370496) + +>>> Fraction(1.2).limit_denominator() +Fraction(6, 5) +``` + +## Arithmetic with Fractions + + +The usual [`arithmetic operators`][operators] `+ - * / **` work with fractions, as with other numeric types. + +Integers and other `Fraction`s can be included and give a `Fraction` result. +Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision. + + +```python +>>> Fraction(2, 3) + Fraction(1, 4) # addition +Fraction(11, 12) + +>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions +Fraction(4, 5) + +>>> Fraction(2, 3) * 6 / 5 # fraction with integers +Fraction(4, 5) + +>>> Fraction(2, 3) * 1.2 # fraction with float -> float +0.7999999999999999 + +>>> Fraction(2, 3) ** 2 # exponentiation with integer +Fraction(4, 9) +``` + +## Conversions to and from Fractions + + +Fractions are great for preserving precision during intermediate calculations, but may not be what you want for the final output. + +It is possible to get the numerator and denominator individually or as a tuple ([`tuples`][tuple] will be discussed in a later Concept): + +```python +>>> Fraction(2, 3).numerator +2 +>>> Fraction(2, 3).denominator +3 +>>> Fraction(2, 3).as_integer_ratio() +(2, 3) +``` + +Various standard Python numeric functions also give the result you might expect from working with `int` and `float` types: + +```python +>>> round(Fraction(11, 3)) +4 + +>>> from math import floor, ceil +>>> floor(Fraction(11, 3)) +3 +>>> ceil(Fraction(11, 3)) +4 + +>>> float(Fraction(11, 3)) +3.6666666666666665 +``` + +[fractions]: https://docs.python.org/3/library/fractions.html +[0.30000000000000004]: https://0.30000000000000004.com/ +[fp-issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues +[tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences + +[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[rational]: https://en.wikipedia.org/wiki/Rational_number +[limit_denominator]: https://docs.python.org/3/library/fractions.html diff --git a/concepts/fractions/introduction.md b/concepts/fractions/introduction.md new file mode 100644 index 0000000000..437ccbbeb0 --- /dev/null +++ b/concepts/fractions/introduction.md @@ -0,0 +1,85 @@ +# Introduction + +The [`Fractions`][fractions] module allows us to create and work with [`rational numbers`][rational]: fractions with an integer numerator divided by an integer denominator. +For example, we can store `2/3` as an exact fraction instead of the approximate `float` value `0.6666...`. + +Unlike `int`, `float`, and `complex` numbers, fractions do not have a literal form. +However, the fractions constructor is quite flexible. + +Most obviously, it can take take two integers as arguments. +Common factors are automatically removed, converting the fraction to its "lowest form": the smallest integers that accurately represent the fraction: + +```python +>>> from fractions import Fraction + +>>> f1 = Fraction(2, 3) # 2/3 +>>> f1 +Fraction(2, 3) + +>>> f2 = Fraction(6, 9) +>>> f2 +Fraction(2, 3) # automatically simplified + +>>> f1 == f2 +True +``` + +The fractions constructor can also parse a string representation: + +```python +>>> f3 = Fraction('2/3') +>>> f3 +Fraction(2, 3) +``` + +Fractions can also work with `float` parameters, but this may run into problems with the approximate nature of representing the decimal value internally as binary. +For more on this representation issue, see the [0.30000000000000004][0.30000000000000004] website, and [Floating Point Arithmetic: Issues and Limitations ][fp-issues] in the Python documentation. + +For a more reliable result when using floats with fractions, there is the `.limit_denominator()` method. + + +## Arithmetic with Fractions + +The usual [`arithmetic operators`][operators] `+ - * / **` will work with fractions, as with other numeric types. + +Integers and other `Fraction`s can be included in the equation and give a `Fraction` result. +Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision: + +```python +>>> Fraction(2, 3) + Fraction(1, 4) # addition +Fraction(11, 12) + +>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions +Fraction(4, 5) + +>>> Fraction(2, 3) * 6 / 5 # fraction with integers +Fraction(4, 5) + +>>> Fraction(2, 3) * 1.2 # fraction with float -> float +0.7999999999999999 + +>>> Fraction(2, 3) ** 2 # exponentiation with integer +Fraction(4, 9) +``` + +Various standard Python numeric functions also give the result you might expect from working with `int` and `float` types: + +```python +>>> round(Fraction(11, 3)) +4 + +>>> from math import floor, ceil +>>> floor(Fraction(11, 3)) +3 +>>> ceil(Fraction(11, 3)) +4 + +>>> float(Fraction(11, 3)) +3.6666666666666665 +``` + +[0.30000000000000004]: https://0.30000000000000004.com/ +[fp-issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues +[fractions]: https://docs.python.org/3/library/fractions.html +[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[rational]: https://en.wikipedia.org/wiki/Rational_number diff --git a/concepts/fractions/links.json b/concepts/fractions/links.json new file mode 100644 index 0000000000..78d349bcfc --- /dev/null +++ b/concepts/fractions/links.json @@ -0,0 +1,18 @@ +[ + { + "url": "https://docs.python.org/3/library/fractions.html/", + "description": "Documentation for the Fractions module." + }, + { + "url": "https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues", + "description": "Limitations of Floating Point Arithmetic." + }, + { + "url": "https://leancrew.com/all-this/2023/08/decimal-to-fraction/", + "description": "And now it's all this: Decimal to fraction." + }, + { + "url": "https://nrich.maths.org/2515", + "description": "History of Fractions." + } +] diff --git a/config.json b/config.json index 98891388c2..734fa7a98f 100644 --- a/config.json +++ b/config.json @@ -2567,6 +2567,11 @@ "uuid": "565f7618-4552-4eb0-b829-d6bacd03deaf", "slug": "with-statement", "name": "With Statement" + }, + { + "uuid": "000e7768-38b9-4904-9ae2-9a4e448f366c", + "slug": "fractions", + "name": "Fractions" } ], "key_features": [ From cb75332df9619e50d4b8ad3131072a0470a56f9b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 5 Dec 2023 14:03:51 -0800 Subject: [PATCH 559/826] Update config.json to add Fractions Concept (#3559) --- config.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.json b/config.json index 734fa7a98f..26d29574b3 100644 --- a/config.json +++ b/config.json @@ -2373,6 +2373,11 @@ "slug": "enums", "name": "Enums" }, + { + "uuid": "8ac7c6b5-5786-45dd-8ce3-5b139da06471", + "slug": "fractions", + "name": "Fractions" + }, { "uuid": "26b147e0-2cdc-4325-a6b4-6a2dd5bb69b1", "slug": "function-arguments", From a706592185a8044dbf69c312cd934873a2156abe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:05:42 -0800 Subject: [PATCH 560/826] Bump actions/setup-python from 4.7.1 to 4.8.0 (#3560) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.7.1 to 4.8.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236...b64ffcaf5b410884ad320a9cfac8866006a109aa) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 1632f55e2e..e79f0b549e 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 + uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 + - uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa with: python-version: ${{ matrix.python-version }} From 0374d1ca286486a1f77aa753e18ba66e297576cb Mon Sep 17 00:00:00 2001 From: colinleach Date: Wed, 6 Dec 2023 18:48:23 -0700 Subject: [PATCH 561/826] new `random` concept (#3556) * new `random` concept * changed unused variables to underscore * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md I have been avoiding $\LaTeX$ (very reluctantly, but Jeremy and Erik insist). I guess Unicode will save us here. Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Added Introduction.md and Links * Small touchups and link fixes * More Typo Fixes --------- Co-authored-by: BethanyG --- concepts/random/.meta/config.json | 5 + concepts/random/about.md | 245 ++++++++++++++++++++++++++++++ concepts/random/introduction.md | 108 +++++++++++++ concepts/random/links.json | 14 ++ config.json | 5 + 5 files changed, 377 insertions(+) create mode 100644 concepts/random/.meta/config.json create mode 100644 concepts/random/about.md create mode 100644 concepts/random/introduction.md create mode 100644 concepts/random/links.json diff --git a/concepts/random/.meta/config.json b/concepts/random/.meta/config.json new file mode 100644 index 0000000000..7319e329ba --- /dev/null +++ b/concepts/random/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "The random module contains functionality to generate random values for modelling, simulations and games. It should not be used for security or cryptographic applications.", + "authors": ["BethanyG", "colinleach"], + "contributors": [] +} diff --git a/concepts/random/about.md b/concepts/random/about.md new file mode 100644 index 0000000000..9ed984179d --- /dev/null +++ b/concepts/random/about.md @@ -0,0 +1,245 @@ +# About + +Many programs need (apparently) random values to simulate real-world events. + +Common, familiar examples include: +- A coin toss: a random value from `('H', 'T')`. +- The roll of a die: a random integer from 1 to 6. +- Shuffling a deck of cards: a random ordering of a card list. + +Generating truly random values with a computer is a [surprisingly difficult technical challenge][truly-random], so you may see these results referred to as "pseudorandom". + +In practice, a well-designed library like the [`random`][random] module in the Python standard library is fast, flexible, and gives results that are amply good enough for most applications in modelling, simulation and games. + +The rest of this page will list a few of the most common functions in `random`. +We encourage you to explore the full `random` documentation, as there are many more options than what we cover here. + + + +~~~~exercism/caution + +The `random` module should __NOT__ be used for security and cryptographic applications. + +Instead, Python provides the [`secrets`][secrets] module. +This is specially optimized for cryptographic security. +Some of the prior issues and reasons for creating the secrets module can be found in [PEP 506][PEP 506]. + +[secrets]: https://docs.python.org/3.11/library/secrets.html#module-secrets +[PEP 506]: https://peps.python.org/pep-0506/ +~~~~ + + + +## Importing + +Before you can utilize the tools in the `random` module, you must first import it: + +```python +>>> import random + +# Choose random integer from a range +>>> random.randrange(1000) +360 + +>>> random.randrange(-1, 500) +228 + +>>> random.randrange(-10, 11, 2) +-8 + +# Choose random integer between two values (inclusive) +>>> random.randint(5, 25) +22 + +``` + +To avoid typing the name of the module, you can import specific functions by name: + +```python +>>> from random import choice, choices + +# Using choice() to pick Heads or Tails 10 times +>>> tosses = [] +>>> for side in range(10): +>>> tosses.append(choice(['H', 'T'])) + +>>> print(tosses) +['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H'] + + +# Using choices() to pick Heads or Tails 8 times +>>> picks = [] +>>> picks.extend(choices(['H', 'T'], k=8)) +>>> print(picks) +['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T'] +``` + + +## Creating random integers + +The `randrange()` function has three forms, to select a random value from `range(start, stop, step)`: + 1. `randrange(stop)` gives an integer `n` such that `0 <= n < stop` + 2. `randrange(start, stop)` gives an integer `n` such that `start <= n < stop` + 3. `randrange(start, stop, step)` gives an integer `n` such that `start <= n < stop` and `n` is in the sequence `start, start + step, start + 2*step...` + +For the common case where `step == 1`, the `randint(a, b)` function may be more convenient and readable. +Possible results from `randint()` _include_ the upper bound, so `randint(a, b)` is the same as using `randrange(a, b+1)`: + +```python +>>> import random + +# Select one number at random from the range 0, 499 +>>> random.randrange(500) +219 + +# Select 10 numbers at random between 0 and 9 two steps apart. +>>> numbers = [] +>>> for integer in range(10): +>>> numbers.append(random.randrange(0, 10, 2)) +>>> print(numbers) +[2, 8, 4, 0, 4, 2, 6, 6, 8, 8] + +# roll a die +>>> random.randint(1, 6) +4 +``` + + + +## Working with sequences + +The functions in this section assume that you are starting from some [sequence][sequence-types], or other container. + + +This will typically be a `list`, or with some limitations a `tuple` or a `set` (_a `tuple` is immutable, and `set` is unordered_). + + + +### `choice()` and `choices()` + +The `choice()` function will return one entry chosen at random from a given sequence. +At its simplest, this might be a coin-flip: + +```python +# This will pick one of the two values in the list at random 5 separate times +>>> [random.choice(['H', 'T']) for _ in range(5)] +['T', 'H', 'H', 'T', 'H'] + +We could accomplish essentially the same thing using the `choices()` function, supplying a keyword argument with the list length: + + +```python +>>> random.choices(['H', 'T'], k=5) +['T', 'H', 'T', 'H', 'H'] +``` + + +In the examples above, we assumed a fair coin with equal probability of heads or tails, but weights can also be specified. +For example, if a bag contains 10 red balls and 15 green balls, and we would like to pull one out at random: + +```python +>>> random.choices(['red', 'green'], [10, 15]) +['red'] +``` + + + +### `sample()` + +The `choices()` example above assumes what statisticians call ["sampling with replacement"][sampling-with-replacement]. +Each pick or choice has **no effect** on the probability of future choices, and the distribution of potential choices remains the same from pick to pick. + + +In the example with red and green balls: after each choice, we _return_ the ball to the bag and shake well before the next pick. +This is in contrast to a situation where we pull out a red ball and _it stays out_. +Not returning the ball means there are now fewer red balls in the bag, and the next choice is now _less likely_ to be red. + +To simulate this "sampling without replacement", the random module provides the `sample()` function. +The syntax of `sample()` is similar to `choices()`, except it adds a `counts` keyword parameter: + + +```python +>>> random.sample(['red', 'green'], counts=[10, 15], k=10) +['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green'] +``` + +Samples are returned in the order they were chosen. + + + +### `shuffle()` + +Both `choices()` and `sample()` return new lists when `k > 1`. +In contrast, `shuffle()` randomizes the order of a list _**in place**_, and the original ordering is lost: + +```python +>>> my_list = [1, 2, 3, 4, 5] +>>> random.shuffle(my_list) +>>> my_list +[4, 1, 5, 2, 3] +``` + + +## Working with Distributions + +Until now, we have concentrated on cases where all outcomes are equally likely. +For example, `random.randrange(100)` is equally likely to give any integer from 0 to 99. + +Many real-world situations are far less simple than this. +As a result, statisticians have created a wide variety of [`distributions`][probability-distribution] to describe "real world" results mathematically. + + + +### Uniform distributions + +For integers, `randrange()` and `randint()` are used when all probabilities are equal. +This is called a [`uniform`][uniform-distribution] distribution. + + +There are floating-point equivalents to `randrange()` and `randint()`. + +__`random()`__ gives a `float` value `x` such that `0.0 <= x < 1.0`. + +__`uniform(a, b)`__ gives `x` such that `a <= x <= b`. + +```python +>>> [round(random.random(), 3) for _ in range(5)] +[0.876, 0.084, 0.483, 0.22, 0.863] + +>>> [round(random.uniform(2, 5), 3) for _ in range(5)] +[2.798, 2.539, 3.779, 3.363, 4.33] +``` + + + +### Gaussian distribution + +Also called the "normal" distribution or the "bell-shaped" curve, this is a very common way to describe imprecision in measured values. + +For example, suppose the factory where you work has just bought 10,000 bolts which should be identical. +You want to set up the factory robot to handle them, so you weigh a sample of 100 and find that they have an average (or `mean`) weight of 4.731g. +This is extremely unlikely to mean that they all weigh exactly 4.731g. +Perhaps you find that values range from 4.627 to 4.794g but cluster around 4.731g. + +This is the [`Gaussian distribution`][gaussian-distribution], for which probabilities peak at the mean and tails off symmetrically on both sides (hence "bell-shaped"). +To simulate this in software, we need some way to specify the width of the curve (_typically, expensive bolts will cluster more tightly around the mean than cheap bolts!_). + +By convention, this is done with the [`standard deviation`][standard-deviation]: small values for a sharp, narrow curve, large for a low, broad curve. +Mathematicians love Greek letters, so we use `μ` ('mu') to represent the mean and `σ` ('sigma') to represent the standard deviation. +Thus, if you read that "95% of values are within 2σ of μ" or "the Higgs boson has been detected with 5-sigma confidence", such comments relate to the standard deviation. + +```python +>>> mu = 4.731 +>>> sigma = 0.316 +>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)] +[4.72, 4.957, 4.64, 4.556, 4.968] +``` + +[gaussian-distribution]: https://simple.wikipedia.org/wiki/Normal_distribution +[probability-distribution]: https://simple.wikipedia.org/wiki/Probability_distribution +[random]: https://docs.python.org/3/library/random.html +[sampling-with-replacement]: https://www.youtube.com/watch?v=LnGFL_A6A6A +[sequence-types]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[standard-deviation]: https://simple.wikipedia.org/wiki/Standard_deviation +[truly-random]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random +[uniform-distribution]: https://www.investopedia.com/terms/u/uniform-distribution.asp#:~:text=In%20statistics%2C%20uniform%20distribution%20refers,a%20spade%20is%20equally%20likely. diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md new file mode 100644 index 0000000000..6bf880be57 --- /dev/null +++ b/concepts/random/introduction.md @@ -0,0 +1,108 @@ +# Introduction + +Many programs need (apparently) random values to simulate real-world events. + +Common, familiar examples include: +- A coin toss: a random value from `('H', 'T')`. +- The roll of a die: a random integer from 1 to 6. +- Shuffling a deck of cards: a random ordering of a card list. +- The creation of trees and bushes in a 3-D graphics simulation. + +Generating _truly_ random values with a computer is a [surprisingly difficult technical challenge][truly-random], so you may see these results referred to as "pseudorandom". + +In practice, a well-designed library like the [`random`][random] module in the Python standard library is fast, flexible, and gives results that are amply good enough for most applications in modelling, simulation and games. + +For this brief introduction, we show the four most commonly used functions from the module. +We encourage you to explore the full [`random`][random] documentation, as there are many tools and options. + + +~~~~exercism/caution + +The `random` module should __NOT__ be used for security and cryptographic applications!! + +Instead, Python provides the [`secrets`][secrets] module. +This is specially optimized for cryptographic security. +Some of the prior issues and reasons for creating the secrets module can be found in [PEP 506][PEP 506]. + +[secrets]: https://docs.python.org/3.11/library/secrets.html#module-secrets +[PEP 506]: https://peps.python.org/pep-0506/ +~~~~ + + +Before you can utilize the tools in the `random` module, you must first import it: + +```python +>>> import random + +# Choose random integer from a range +>>> random.randrange(1000) +360 + +>>> random.randrange(-1, 500) +228 + +>>> random.randrange(-10, 11, 2) +-8 + +# Choose random integer between two values (inclusive) +>>> random.randint(5, 25) +22 + +``` + +To avoid typing the name of the module, you can import specific functions by name: + +```python +>>> from random import choice, choices + +# Using choice() to pick Heads or Tails 10 times +>>> tosses = [] +>>> for side in range(10): +>>> tosses.append(choice(['H', 'T'])) + +>>> print(tosses) +['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H'] + + +# Using choices() to pick Heads or Tails 8 times +>>> picks = [] +>>> picks.extend(choices(['H', 'T'], k=8)) +>>> print(picks) +['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T'] +``` + + + +## `randrange()` and `randint()` + +Shown in the first example above, the `randrange()` function has three forms: + +1. `randrange(stop)` gives an integer `n` such that `0 <= n < stop` +2. `randrange(start, stop)` gives an integer `n` such that `start <= n < stop` +3. `randrange(start, stop, step)` gives an integer `n` such that `start <= n < stop` + and `n` is in the sequence `start, start + step, start + 2*step...` + +For the most common case where `step == 1`, `randint(a, b)` may be more convenient and readable. +Possible results from `randint()` _include_ the upper bound, so `randint(a, b)` is the same as using `randrange(a, b+1)`. + + + +## `choice()` and `choices()` + +These two functions assume that you are starting from some [sequence][sequence-types], or other container. +This will typically be a `list`, or with some limitations a `tuple` or a `set` (_a `tuple` is immutable, and `set` is unordered_). + +The `choice()` function will return one entry chosen at random from a given sequence, and `choices()` will return `k` number of entries chosen at random from a given sequence. +In the examples shown above, we assumed a fair coin with equal probability of heads or tails, but weights can also be specified. + +For example, if a bag contains 10 red balls and 15 green balls, and we would like to pull one out at random: + + +```python +>>> random.choices(['red', 'green'], [10, 15]) +['red'] +``` + +[random]: https://docs.python.org/3/library/random.html +[sequence-types]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[truly-random]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random diff --git a/concepts/random/links.json b/concepts/random/links.json new file mode 100644 index 0000000000..22f60dbfb4 --- /dev/null +++ b/concepts/random/links.json @@ -0,0 +1,14 @@ +[ + { + "url": "https://docs.python.org/3/library/random.html/", + "description": "Official documentation for the random module." + }, + { + "url": "https://engineering.mit.edu/engage/ask-an-engineer/can-a-computer-generate-a-truly-random-number/", + "description": "MIT Engineering: Can a computer generate a truly random number?" + }, + { + "url": "https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random", + "description": "Are Random Numbers Really Random?" + } +] diff --git a/config.json b/config.json index 26d29574b3..2d59e5b0dc 100644 --- a/config.json +++ b/config.json @@ -2573,6 +2573,11 @@ "slug": "with-statement", "name": "With Statement" }, + { + "uuid": "af6cad74-50c2-48f4-a6ce-cfeb72548d00", + "slug": "random", + "name": "Random" + }, { "uuid": "000e7768-38b9-4904-9ae2-9a4e448f366c", "slug": "fractions", From d7fe4d0d58102766d77bef588e75a3b35b6a0802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:49:04 -0800 Subject: [PATCH 562/826] Bump actions/setup-python from 4.8.0 to 5.0.0 (#3561) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.8.0 to 5.0.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/b64ffcaf5b410884ad320a9cfac8866006a109aa...0a5c61591373683505ea898e09a3ea4f39ef2b9c) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index e79f0b549e..944c2f531f 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Set up Python - uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: python-version: ${{ matrix.python-version }} From 8ebf5575ad2a69a8799f7c4e3f5b535a1ce466fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:03:54 -0800 Subject: [PATCH 563/826] Bump actions/stale from 8.0.0 to 9.0.0 (#3562) Bumps [actions/stale](https://github.com/actions/stale) from 8.0.0 to 9.0.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/1160a2240286f5da8ec72b1c0816ce2481aabf84...28ca1036281a5e5922ead5184a1bbf96e5fc984e) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 86f881a21c..f526390311 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-22.04 steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 From 0b7819512d37eb87149d3d9f3ce824c31cc6e8ad Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Mon, 18 Dec 2023 11:00:03 +0530 Subject: [PATCH 564/826] approach for nth prime (#3496) --- .../nth-prime/.approaches/config.json | 21 ++++++ .../.approaches/generator-fun/content.md | 51 +++++++++++++++ .../.approaches/generator-fun/snippet.txt | 6 ++ .../nth-prime/.approaches/introduction.md | 46 +++++++++++++ .../nth-prime/.approaches/tracking/content.md | 65 +++++++++++++++++++ .../.approaches/tracking/snippet.txt | 8 +++ 6 files changed, 197 insertions(+) create mode 100644 exercises/practice/nth-prime/.approaches/config.json create mode 100644 exercises/practice/nth-prime/.approaches/generator-fun/content.md create mode 100644 exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt create mode 100644 exercises/practice/nth-prime/.approaches/introduction.md create mode 100644 exercises/practice/nth-prime/.approaches/tracking/content.md create mode 100644 exercises/practice/nth-prime/.approaches/tracking/snippet.txt diff --git a/exercises/practice/nth-prime/.approaches/config.json b/exercises/practice/nth-prime/.approaches/config.json new file mode 100644 index 0000000000..00bbaff5fc --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": ["safwansamsudeen"] + }, + "approaches": [ + { + "uuid": "c97a3f8e-a97d-4e45-b44f-128bcffb2d3a", + "slug": "generator-fun", + "title": "Generator Fun", + "blurb": "Utilize Python library and generators", + "authors": ["safwansamsudeen"] + }, + { + "uuid": "e989fdd2-3f39-4195-9d7c-120a6d6376b6", + "slug": "tracking", + "title": "Tracking", + "blurb": "Track previous prime values and return the nth one", + "authors": ["safwansamsudeen"] + } + ] +} diff --git a/exercises/practice/nth-prime/.approaches/generator-fun/content.md b/exercises/practice/nth-prime/.approaches/generator-fun/content.md new file mode 100644 index 0000000000..39290335a0 --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/generator-fun/content.md @@ -0,0 +1,51 @@ +# Generator Fun +The key of this approach is to not store the elements you do not need. + +This is a code representation: +```python +from itertools import islice, count + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(lambda counter: all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)), count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` + +Let's dissect it! `itertools.count` is like `range` without un upper bound - calling it returns a generator, and `for ... in count_obj` will result in an infinite loop. + +Using a lambda expression, we `filter` out any numbers above two that are prime. +Doesn't this result in an infinite loop? +No - `filter` _also_ returns a generator object (which are [evaluated lazily][generator]), so while it's too will produce values infinitely if evaluated, it doesn't hang to program at the time of instantiation. + +`itertools.islice` takes in a generator object and an end count, returning a generator object which _only evalutes until that end count_. + +The next line exhausts all the values in the generator except the end, and we finally return the last element. + +We can utilize the `functools.cache` decorator for greater speeds at higher values of `number`, so we take it out. The added bonus is that a very long line of code is cleant up. + +```python +from itertools import islice, count +from functools import cache + +@cache +def is_prime(counter): + return all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)) + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(is_prime, count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` + +~~~~exercism/note +Note that this that not create a list anywhere, and thus is both memory and time efficient. +~~~~ + +Read more on `itertools` on the [Python docs][itertools]. + +[itertools]: https://docs.python.org/3/library/itertools.html +[generator]: https://www.programiz.com/python-programming/generator \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt b/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt new file mode 100644 index 0000000000..d073667a4c --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt @@ -0,0 +1,6 @@ +from itertools import islice, count + +def prime(number): + gen = islice(filter(lambda n: all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)), count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/introduction.md b/exercises/practice/nth-prime/.approaches/introduction.md new file mode 100644 index 0000000000..e20541778e --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/introduction.md @@ -0,0 +1,46 @@ +# Introduction +Nth Prime in Python is a very interesting exercise that can be solved in multiple ways. + +## General guidance +Every approach has to a) validate the input, b) calculate the prime numbers until n - 1, and c) return the nth prime number. + +As the previous numbers are calculated multiple times, it's advisable to extract that piece of code as a function and use `functools.cache` for greater speeds at higher numbers. + +## Approach: Tracking +Using this approach, we manually track the primes/number of primes until the nth prime, after which we quit the loop and return the last number in the list/currently tracked prime. +There are many possible code implementations, such as this one: +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in primes): + primes.append(counter) + return primes[-1] +``` + +If you're interested in learning more about this approach (and discover a lesser known Python feature!), go [here][approach-tracking]. + +## Approach: Generator Fun +A far more idiomatic approach that utilizes Python's powerful standard library is to use `itertools` and generator expression related functions. + +```python +from itertools import islice, count + +def is_prime(n): + return not any(n % k == 0 for k in range(2, int(n ** 0.5) + 1)) + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(is_prime, count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` +If you'd like to understand this approach better, [read more][approach-generator-fun]. + +[approach-tracking]: https://exercism.org/tracks/python/exercises/nth-prime/approaches/tracking +[approach-generator-fun]: https://exercism.org/tracks/python/exercises/nth-prime/approaches/generator-fun \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/tracking/content.md b/exercises/practice/nth-prime/.approaches/tracking/content.md new file mode 100644 index 0000000000..7e6398c92f --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/tracking/content.md @@ -0,0 +1,65 @@ +# Tracking +This approach includes building a list of all the previous primes until it reaches the nth number, after which it quits the loop and return the last number in the list. +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in range(2, counter)): + primes.append(counter) + return primes[-1] +``` +Efficiency can be improved slightly by reducing the factor check to the square root of the number... +```python +... +if all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)): + ... +``` +... or even better, checking whether a new number is merely not divisible by any of the primes (in which case it's a prime itself): +```python +... +if all(counter % test != 0 for test in primes): + ... +``` +Instead of building the list, it's more memory efficient to just save the number of primes found until now, and return the currently stored number when the nth prime is found. +However, this elongates the code. +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + prime_count = 0 + while True: + isprime = True + for test in range(2, int(counter ** 0.5) + 1): + if counter % test == 0: + isprime = False + break + if isprime: + prime_count += 1 + if prime_count == number: + return counter + counter += 1 +``` + +~~~~exercism/advanced +Tip: you can use `for... else` to make your code more idiomatic here. +```python +... +for test in range(2, int(counter ** 0.5) + 1): + if counter % test == 0: + break +else: + prime_count += 1 +if prime_count == number: + return counter +``` +The else block is executed if the `for` loop completes normally - that is, without `break`ing. +Read more on [for/else][for-else] +~~~~ + + +[for-else]: https://book.pythontips.com/en/latest/for_-_else.html \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/tracking/snippet.txt b/exercises/practice/nth-prime/.approaches/tracking/snippet.txt new file mode 100644 index 0000000000..1e8b3ab883 --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/tracking/snippet.txt @@ -0,0 +1,8 @@ +def prime(number): + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in range(2, counter)): + primes.append(counter) + return primes[-1] \ No newline at end of file From 38caf4b50b9368e00c50c0d3c1bab403513c0692 Mon Sep 17 00:00:00 2001 From: colinleach Date: Tue, 26 Dec 2023 17:46:34 -0700 Subject: [PATCH 565/826] The `secrets` concept (#3565) * The `secrets` concept * Edits and Link Additions A few edits, and some additional links. --------- Co-authored-by: BethanyG --- concepts/secrets/.meta/config.json | 5 +++ concepts/secrets/about.md | 51 ++++++++++++++++++++++++++++++ concepts/secrets/introduction.md | 17 ++++++++++ concepts/secrets/links.json | 18 +++++++++++ config.json | 5 +++ 5 files changed, 96 insertions(+) create mode 100644 concepts/secrets/.meta/config.json create mode 100644 concepts/secrets/about.md create mode 100644 concepts/secrets/introduction.md create mode 100644 concepts/secrets/links.json diff --git a/concepts/secrets/.meta/config.json b/concepts/secrets/.meta/config.json new file mode 100644 index 0000000000..152aa0eb3b --- /dev/null +++ b/concepts/secrets/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "The secrets module is a cryptographically-secure alternative to the random module, intended for security-critical uses.", + "authors": ["BethanyG", "colinleach"], + "contributors": [] +} diff --git a/concepts/secrets/about.md b/concepts/secrets/about.md new file mode 100644 index 0000000000..5987ab37e9 --- /dev/null +++ b/concepts/secrets/about.md @@ -0,0 +1,51 @@ +# About + +A previous concept discussed the [concept:python/random]() module, which produces [pseudo-random numbers][pseudo-random-numbers] and pseudo-random list orderings. + +The [`secrets`][secrets] module overlaps with `random` in some of its functionality, but the two modules are designed with very different priorities. + +- `random` is optimized for high performance in modelling and simulation, with "good enough" pseudo-random number generation. +- `secrets` is designed to be crytographically secure for applications such as password hashing, security token generation, and account authentication. + + +Further details on why the addition of the `secrets` module proved necessary are given in [PEP 506][PEP506]. + +The `secrets` is relatively small and straightforward, with methods for generating random integers, bits, bytes or tokens, or a random entry from a given sequence. + +To use `scerets`, you mush first `import` it: + + +```python +>>> import secrets + +#Returns n, where 0 <= n < 1000. +>>> secrets.randbelow(1000) +577 + +#32-bit integers. +>>> secrets.randbits(32) +3028709440 + +>>> bin(secrets.randbits(32)) +'0b11111000101100101111110011110100' + +#Pick at random from a sequence. +>>> secrets.choice(['my', 'secret', 'thing']) +'thing' + +#Generate a token made up of random hexadecimal digits. +>>> secrets.token_hex() +'f683d093ea9aa1f2607497c837cf11d7afaefa903c5805f94b64f068e2b9e621' + +#Generate a URL-safe token of random alphanumeric characters. +>>> secrets.token_urlsafe(16) +'gkSUKRdiPDHqmImPi2HMnw' +``` + + +If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples. + + +[PEP506]: https://peps.python.org/pep-0506/ +[pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators +[secrets]: https://docs.python.org/3/library/secrets.html diff --git a/concepts/secrets/introduction.md b/concepts/secrets/introduction.md new file mode 100644 index 0000000000..a82538acda --- /dev/null +++ b/concepts/secrets/introduction.md @@ -0,0 +1,17 @@ +# Introduction + +A previous concept discussed the [concept:python/random]() module, which produces [pseudo-random numbers][pseudo-random-numbers] and pseudo-random list orderings. + +The [`secrets`][secrets] module overlaps with `random` in some of its functionality, but the two modules are designed with very different priorities. + +- `random` is optimized for high performance in modelling and simulation, with "good enough" pseudo-random number generation. +- `secrets` is designed to be crytographically secure for applications such as password hashing, security token generation, and account authentication. + + +Further details on why the addition of the `secrets` module proved necessary are given in [PEP 506][PEP506]. + +If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples. + +[PEP506]: https://peps.python.org/pep-0506/[secrets]: https://docs.python.org/3/library/secrets.html +[pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators +[secrets]: https://docs.python.org/3/library/secrets.html diff --git a/concepts/secrets/links.json b/concepts/secrets/links.json new file mode 100644 index 0000000000..ccf44e6fe5 --- /dev/null +++ b/concepts/secrets/links.json @@ -0,0 +1,18 @@ +[ + { + "url": "https://docs.python.org/3/library/secrets.html/", + "description": "The secrets module." + }, + { + "url": "https://peps.python.org/pep-0506/", + "description": "PEP 506, giving reasons why the secrets module is necessary." + }, + { + "url": "https://en.wikipedia.org/wiki/Pseudorandomness", + "description": "Wikipedia: Pseudorandomness." + }, + { + "url": "https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators", + "description": "Khan Academy: Pseudorandom Number Generators." + } +] diff --git a/config.json b/config.json index 2d59e5b0dc..6afd504680 100644 --- a/config.json +++ b/config.json @@ -2582,6 +2582,11 @@ "uuid": "000e7768-38b9-4904-9ae2-9a4e448f366c", "slug": "fractions", "name": "Fractions" + }, + { + "uuid": "e1496136-8d58-4409-9a41-4b6ee4721c6b", + "slug": "secrets", + "name": "Secrets" } ], "key_features": [ From d2be549dc5d8d5dcc136f8b705730c36b6db2966 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 26 Dec 2023 22:59:17 -0800 Subject: [PATCH 566/826] Update introduction.md (#3572) Fixed link typos. --- concepts/secrets/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/secrets/introduction.md b/concepts/secrets/introduction.md index a82538acda..04308ed0a2 100644 --- a/concepts/secrets/introduction.md +++ b/concepts/secrets/introduction.md @@ -12,6 +12,6 @@ Further details on why the addition of the `secrets` module proved necessary are If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples. -[PEP506]: https://peps.python.org/pep-0506/[secrets]: https://docs.python.org/3/library/secrets.html +[PEP506]: https://peps.python.org/pep-0506/ [pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators [secrets]: https://docs.python.org/3/library/secrets.html From 60b19a6bd429e6b1d6cf6ac12ea8a6714ae417e1 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:05:44 -0800 Subject: [PATCH 567/826] Synced practice exercise docs to problem specificatons. (#3573) --- .../perfect-numbers/.docs/instructions.md | 44 +++++++++++++------ .../phone-number/.docs/instructions.md | 6 ++- .../queen-attack/.docs/instructions.md | 20 ++++----- .../practice/rest-api/.docs/instructions.md | 2 +- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/exercises/practice/perfect-numbers/.docs/instructions.md b/exercises/practice/perfect-numbers/.docs/instructions.md index 689a73c00d..b2bc82ca3e 100644 --- a/exercises/practice/perfect-numbers/.docs/instructions.md +++ b/exercises/practice/perfect-numbers/.docs/instructions.md @@ -2,22 +2,38 @@ Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. -The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum][aliquot-sum]. -The aliquot sum is defined as the sum of the factors of a number not including the number itself. +The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of [perfect](#perfect), [abundant](#abundant), or [deficient](#deficient) based on their [aliquot sum][aliquot-sum]. +The _aliquot sum_ is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`. -- **Perfect**: aliquot sum = number - - 6 is a perfect number because (1 + 2 + 3) = 6 - - 28 is a perfect number because (1 + 2 + 4 + 7 + 14) = 28 -- **Abundant**: aliquot sum > number - - 12 is an abundant number because (1 + 2 + 3 + 4 + 6) = 16 - - 24 is an abundant number because (1 + 2 + 3 + 4 + 6 + 8 + 12) = 36 -- **Deficient**: aliquot sum < number - - 8 is a deficient number because (1 + 2 + 4) = 7 - - Prime numbers are deficient - -Implement a way to determine whether a given number is **perfect**. -Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. +## Perfect + +A number is perfect when it equals its aliquot sum. +For example: + +- `6` is a perfect number because `1 + 2 + 3 = 6` +- `28` is a perfect number because `1 + 2 + 4 + 7 + 14 = 28` + +## Abundant + +A number is abundant when it is less than its aliquot sum. +For example: + +- `12` is an abundant number because `1 + 2 + 3 + 4 + 6 = 16` +- `24` is an abundant number because `1 + 2 + 3 + 4 + 6 + 8 + 12 = 36` + +## Deficient + +A number is deficient when it is greater than its aliquot sum. +For example: + +- `8` is a deficient number because `1 + 2 + 4 = 7` +- Prime numbers are deficient + +## Task + +Implement a way to determine whether a given number is [perfect](#perfect). +Depending on your language track, you may also need to implement a way to determine whether a given number is [abundant](#abundant) or [deficient](#deficient). [nicomachus]: https://en.wikipedia.org/wiki/Nicomachus [aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 6b86bbac9f..62ba48e96f 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -11,12 +11,14 @@ The first three digits of the local number represent the _exchange code_, follow The format is usually represented as ```text -(NXX)-NXX-XXXX +NXX NXX-XXXX ``` where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9. -Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code (1) if present. +Sometimes they also have the country code (represented as `1` or `+1`) prefixed. + +Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code if present. For example, the inputs diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md index ad7ea95479..97f22a0aee 100644 --- a/exercises/practice/queen-attack/.docs/instructions.md +++ b/exercises/practice/queen-attack/.docs/instructions.md @@ -8,18 +8,14 @@ A chessboard can be represented by an 8 by 8 array. So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) and the black queen at `f2` (zero-indexed at column 5, row 6), then you know that the set-up is like so: -```text - a b c d e f g h -8 _ _ _ _ _ _ _ _ 8 -7 _ _ _ _ _ _ _ _ 7 -6 _ _ _ _ _ _ _ _ 6 -5 _ _ W _ _ _ _ _ 5 -4 _ _ _ _ _ _ _ _ 4 -3 _ _ _ _ _ _ _ _ 3 -2 _ _ _ _ _ B _ _ 2 -1 _ _ _ _ _ _ _ _ 1 - a b c d e f g h -``` +![A chess board with two queens. Arrows emanating from the queen at c5 indicate possible directions of capture along file, rank and diagonal.](https://assets.exercism.org/images/exercises/queen-attack/queen-capture.svg) You are also able to answer whether the queens can attack each other. In this case, that answer would be yes, they can, because both pieces share a diagonal. + +## Credit + +The chessboard image was made by [habere-et-dispertire][habere-et-dispertire] using LaTeX and the [chessboard package][chessboard-package] by Ulrike Fischer. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[chessboard-package]: https://github.com/u-fischer/chessboard diff --git a/exercises/practice/rest-api/.docs/instructions.md b/exercises/practice/rest-api/.docs/instructions.md index cb57f6f43f..af223ba4b4 100644 --- a/exercises/practice/rest-api/.docs/instructions.md +++ b/exercises/practice/rest-api/.docs/instructions.md @@ -44,5 +44,5 @@ Your task is to implement a simple [RESTful API][restful-wikipedia] that receive [restful-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer [iou]: https://en.wikipedia.org/wiki/IOU [github-rest]: https://developer.github.com/v3/ -[reddit-rest]: https://www.reddit.com/dev/api/ +[reddit-rest]: https://web.archive.org/web/20231202231149/https://www.reddit.com/dev/api/ [restfulapi]: https://restfulapi.net/ From 0bb0a2fd810e36391ff87c5e944efa7ced81bbf0 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:06:00 -0800 Subject: [PATCH 568/826] Synced practice exercise metadata to problem specifications. (#3574) --- exercises/practice/hello-world/.meta/config.json | 2 +- exercises/practice/pop-count/.meta/config.json | 2 +- exercises/practice/pythagorean-triplet/.meta/config.json | 2 +- exercises/practice/spiral-matrix/.meta/config.json | 2 +- exercises/practice/transpose/.meta/config.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/practice/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json index fc3599fd07..16a87cf554 100644 --- a/exercises/practice/hello-world/.meta/config.json +++ b/exercises/practice/hello-world/.meta/config.json @@ -27,7 +27,7 @@ ".meta/example.py" ] }, - "blurb": "The classical introductory exercise. Just say \"Hello, World!\".", + "blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".", "source": "This is an exercise to introduce users to using Exercism", "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" } diff --git a/exercises/practice/pop-count/.meta/config.json b/exercises/practice/pop-count/.meta/config.json index 36b1259ace..0c402acfad 100644 --- a/exercises/practice/pop-count/.meta/config.json +++ b/exercises/practice/pop-count/.meta/config.json @@ -13,7 +13,7 @@ ".meta/example.py" ] }, - "blurb": "Help Eluid count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", + "blurb": "Help Eliud count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", "source": "Christian Willner, Eric Willigers", "source_url": "https://forum.exercism.org/t/new-exercise-suggestion-pop-count/7632/5" } diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 03d35139ac..a9bed96083 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -31,7 +31,7 @@ ".meta/example.py" ] }, - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product a * b * c.", + "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the triplet.", "source": "Problem 9 at Project Euler", "source_url": "https://projecteuler.net/problem=9" } diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index f6dfd5a505..b2e3dd9f23 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -22,5 +22,5 @@ }, "blurb": "Given the size, return a square matrix of numbers in spiral order.", "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", - "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" + "source_url": "https://web.archive.org/web/20230607064729/https://old.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" } diff --git a/exercises/practice/transpose/.meta/config.json b/exercises/practice/transpose/.meta/config.json index 5eb2b08ef7..45df315124 100644 --- a/exercises/practice/transpose/.meta/config.json +++ b/exercises/practice/transpose/.meta/config.json @@ -27,5 +27,5 @@ }, "blurb": "Take input text and output it transposed.", "source": "Reddit r/dailyprogrammer challenge #270 [Easy].", - "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/4msu2x/challenge_270_easy_transpose_the_input_text" + "source_url": "https://web.archive.org/web/20230630051421/https://old.reddit.com/r/dailyprogrammer/comments/4msu2x/challenge_270_easy_transpose_the_input_text/" } From ef951b40b8da66f51d8021f1db58fb5ca0f3a7bf Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:06:28 -0800 Subject: [PATCH 569/826] Synced tests to cannonical data and regenerated testfile. (#3575) --- exercises/practice/anagram/.meta/tests.toml | 5 +++++ exercises/practice/anagram/anagram_test.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml index 8a3708bbf9..4f43811d26 100644 --- a/exercises/practice/anagram/.meta/tests.toml +++ b/exercises/practice/anagram/.meta/tests.toml @@ -46,6 +46,11 @@ description = "detects anagrams using case-insensitive possible matches" [7cc195ad-e3c7-44ee-9fd2-d3c344806a2c] description = "does not detect an anagram if the original word is repeated" +include = false + +[630abb71-a94e-4715-8395-179ec1df9f91] +description = "does not detect an anagram if the original word is repeated" +reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c" [9878a1c9-d6ea-4235-ae51-3ea2befd6842] description = "anagrams must use all letters exactly once" diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py index 5b0e9c6c4d..bed1d80fc9 100644 --- a/exercises/practice/anagram/anagram_test.py +++ b/exercises/practice/anagram/anagram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2023-07-20 +# File last updated on 2023-12-27 import unittest @@ -61,7 +61,7 @@ def test_detects_anagrams_using_case_insensitive_possible_matches(self): self.assertCountEqual(find_anagrams("orchestra", candidates), expected) def test_does_not_detect_an_anagram_if_the_original_word_is_repeated(self): - candidates = ["go Go GO"] + candidates = ["goGoGO"] expected = [] self.assertCountEqual(find_anagrams("go", candidates), expected) From 66160b0b30474e5285dfc7ccae3c09046bd85182 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:06:50 -0800 Subject: [PATCH 570/826] Synced tests to problem specifications. (#3576) --- .../practice/custom-set/.meta/tests.toml | 92 ++++++++++--------- .../practice/custom-set/custom_set_test.py | 7 +- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/exercises/practice/custom-set/.meta/tests.toml b/exercises/practice/custom-set/.meta/tests.toml index 6ba6231595..8c450e0baf 100644 --- a/exercises/practice/custom-set/.meta/tests.toml +++ b/exercises/practice/custom-set/.meta/tests.toml @@ -1,117 +1,127 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [20c5f855-f83a-44a7-abdd-fe75c6cf022b] -description = "sets with no elements are empty" +description = "Returns true if the set contains no elements -> sets with no elements are empty" [d506485d-5706-40db-b7d8-5ceb5acf88d2] -description = "sets with elements are not empty" +description = "Returns true if the set contains no elements -> sets with elements are not empty" [759b9740-3417-44c3-8ca3-262b3c281043] -description = "nothing is contained in an empty set" +description = "Sets can report if they contain an element -> nothing is contained in an empty set" [f83cd2d1-2a85-41bc-b6be-80adbff4be49] -description = "when the element is in the set" +description = "Sets can report if they contain an element -> when the element is in the set" [93423fc0-44d0-4bc0-a2ac-376de8d7af34] -description = "when the element is not in the set" +description = "Sets can report if they contain an element -> when the element is not in the set" [c392923a-637b-4495-b28e-34742cd6157a] -description = "empty set is a subset of another empty set" +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of another empty set" [5635b113-be8c-4c6f-b9a9-23c485193917] -description = "empty set is a subset of non-empty set" +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of non-empty set" [832eda58-6d6e-44e2-92c2-be8cf0173cee] -description = "non-empty set is not a subset of empty set" +description = "A set is a subset if all of its elements are contained in the other set -> non-empty set is not a subset of empty set" [c830c578-8f97-4036-b082-89feda876131] -description = "set is a subset of set with exact same elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of set with exact same elements" [476a4a1c-0fd1-430f-aa65-5b70cbc810c5] -description = "set is a subset of larger set with same elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of larger set with same elements" [d2498999-3e46-48e4-9660-1e20c3329d3d] -description = "set is not a subset of set that does not contain its elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is not a subset of set that does not contain its elements" [7d38155e-f472-4a7e-9ad8-5c1f8f95e4cc] -description = "the empty set is disjoint with itself" +description = "Sets are disjoint if they share no elements -> the empty set is disjoint with itself" [7a2b3938-64b6-4b32-901a-fe16891998a6] -description = "empty set is disjoint with non-empty set" +description = "Sets are disjoint if they share no elements -> empty set is disjoint with non-empty set" [589574a0-8b48-48ea-88b0-b652c5fe476f] -description = "non-empty set is disjoint with empty set" +description = "Sets are disjoint if they share no elements -> non-empty set is disjoint with empty set" [febeaf4f-f180-4499-91fa-59165955a523] -description = "sets are not disjoint if they share an element" +description = "Sets are disjoint if they share no elements -> sets are not disjoint if they share an element" [0de20d2f-c952-468a-88c8-5e056740f020] -description = "sets are disjoint if they share no elements" +description = "Sets are disjoint if they share no elements -> sets are disjoint if they share no elements" [4bd24adb-45da-4320-9ff6-38c044e9dff8] -description = "empty sets are equal" +description = "Sets with the same elements are equal -> empty sets are equal" [f65c0a0e-6632-4b2d-b82c-b7c6da2ec224] -description = "empty set is not equal to non-empty set" +description = "Sets with the same elements are equal -> empty set is not equal to non-empty set" [81e53307-7683-4b1e-a30c-7e49155fe3ca] -description = "non-empty set is not equal to empty set" +description = "Sets with the same elements are equal -> non-empty set is not equal to empty set" [d57c5d7c-a7f3-48cc-a162-6b488c0fbbd0] -description = "sets with the same elements are equal" +description = "Sets with the same elements are equal -> sets with the same elements are equal" [dd61bafc-6653-42cc-961a-ab071ee0ee85] -description = "sets with different elements are not equal" +description = "Sets with the same elements are equal -> sets with different elements are not equal" [06059caf-9bf4-425e-aaff-88966cb3ea14] -description = "set is not equal to larger set with same elements" +description = "Sets with the same elements are equal -> set is not equal to larger set with same elements" + +[d4a1142f-09aa-4df9-8b83-4437dcf7ec24] +description = "Sets with the same elements are equal -> set is equal to a set constructed from an array with duplicates" [8a677c3c-a658-4d39-bb88-5b5b1a9659f4] -description = "add to empty set" +description = "Unique elements can be added to a set -> add to empty set" [0903dd45-904d-4cf2-bddd-0905e1a8d125] -description = "add to non-empty set" +description = "Unique elements can be added to a set -> add to non-empty set" [b0eb7bb7-5e5d-4733-b582-af771476cb99] -description = "adding an existing element does not change the set" +description = "Unique elements can be added to a set -> adding an existing element does not change the set" [893d5333-33b8-4151-a3d4-8f273358208a] -description = "intersection of two empty sets is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of two empty sets is an empty set" [d739940e-def2-41ab-a7bb-aaf60f7d782c] -description = "intersection of an empty set and non-empty set is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of an empty set and non-empty set is an empty set" [3607d9d8-c895-4d6f-ac16-a14956e0a4b7] -description = "intersection of a non-empty set and an empty set is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of a non-empty set and an empty set is an empty set" [b5120abf-5b5e-41ab-aede-4de2ad85c34e] -description = "intersection of two sets with no shared elements is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of two sets with no shared elements is an empty set" [af21ca1b-fac9-499c-81c0-92a591653d49] -description = "intersection of two sets with shared elements is a set of the shared elements" +description = "Intersection returns a set of all shared elements -> intersection of two sets with shared elements is a set of the shared elements" [c5e6e2e4-50e9-4bc2-b89f-c518f015b57e] -description = "difference of two empty sets is an empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two empty sets is an empty set" [2024cc92-5c26-44ed-aafd-e6ca27d6fcd2] -description = "difference of empty set and non-empty set is an empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of empty set and non-empty set is an empty set" [e79edee7-08aa-4c19-9382-f6820974b43e] -description = "difference of a non-empty set and an empty set is the non-empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of a non-empty set and an empty set is the non-empty set" [c5ac673e-d707-4db5-8d69-7082c3a5437e] -description = "difference of two non-empty sets is a set of elements that are only in the first set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two non-empty sets is a set of elements that are only in the first set" [c45aed16-5494-455a-9033-5d4c93589dc6] -description = "union of empty sets is an empty set" +description = "Union returns a set of all elements in either set -> union of empty sets is an empty set" [9d258545-33c2-4fcb-a340-9f8aa69e7a41] -description = "union of an empty set and non-empty set is the non-empty set" +description = "Union returns a set of all elements in either set -> union of an empty set and non-empty set is the non-empty set" [3aade50c-80c7-4db8-853d-75bac5818b83] -description = "union of a non-empty set and empty set is the non-empty set" +description = "Union returns a set of all elements in either set -> union of a non-empty set and empty set is the non-empty set" [a00bb91f-c4b4-4844-8f77-c73e2e9df77c] -description = "union of non-empty sets contains all unique elements" +description = "Union returns a set of all elements in either set -> union of non-empty sets contains all unique elements" diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index 9ee2bd1967..edd7656413 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest @@ -115,6 +115,11 @@ def test_set_is_not_equal_to_larger_set_with_same_elements(self): set2 = CustomSet([1, 2, 3, 4]) self.assertNotEqual(set1, set2) + def test_set_is_equal_to_a_set_constructed_from_an_array_with_duplicates(self): + set1 = CustomSet([1]) + set2 = CustomSet([1, 1]) + self.assertEqual(set1, set2) + def test_add_to_empty_set(self): sut = CustomSet() expected = CustomSet([3]) From b4dbac454c8eab71f36d2e4849ec8d877cb840c9 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:07:16 -0800 Subject: [PATCH 571/826] Synced exercise tests to problem specifications. (#3577) --- .../practice/dnd-character/.meta/tests.toml | 50 ++++++++++++------- .../dnd-character/dnd_character_test.py | 7 ++- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/exercises/practice/dnd-character/.meta/tests.toml b/exercises/practice/dnd-character/.meta/tests.toml index 5d9d1aa34a..719043b253 100644 --- a/exercises/practice/dnd-character/.meta/tests.toml +++ b/exercises/practice/dnd-character/.meta/tests.toml @@ -1,54 +1,61 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37] -description = "ability modifier for score 3 is -4" +description = "ability modifier -> ability modifier for score 3 is -4" [cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c] -description = "ability modifier for score 4 is -3" +description = "ability modifier -> ability modifier for score 4 is -3" [5b519fcd-6946-41ee-91fe-34b4f9808326] -description = "ability modifier for score 5 is -3" +description = "ability modifier -> ability modifier for score 5 is -3" [dc2913bd-6d7a-402e-b1e2-6d568b1cbe21] -description = "ability modifier for score 6 is -2" +description = "ability modifier -> ability modifier for score 6 is -2" [099440f5-0d66-4b1a-8a10-8f3a03cc499f] -description = "ability modifier for score 7 is -2" +description = "ability modifier -> ability modifier for score 7 is -2" [cfda6e5c-3489-42f0-b22b-4acb47084df0] -description = "ability modifier for score 8 is -1" +description = "ability modifier -> ability modifier for score 8 is -1" [c70f0507-fa7e-4228-8463-858bfbba1754] -description = "ability modifier for score 9 is -1" +description = "ability modifier -> ability modifier for score 9 is -1" [6f4e6c88-1cd9-46a0-92b8-db4a99b372f7] -description = "ability modifier for score 10 is 0" +description = "ability modifier -> ability modifier for score 10 is 0" [e00d9e5c-63c8-413f-879d-cd9be9697097] -description = "ability modifier for score 11 is 0" +description = "ability modifier -> ability modifier for score 11 is 0" [eea06f3c-8de0-45e7-9d9d-b8cab4179715] -description = "ability modifier for score 12 is +1" +description = "ability modifier -> ability modifier for score 12 is +1" [9c51f6be-db72-4af7-92ac-b293a02c0dcd] -description = "ability modifier for score 13 is +1" +description = "ability modifier -> ability modifier for score 13 is +1" [94053a5d-53b6-4efc-b669-a8b5098f7762] -description = "ability modifier for score 14 is +2" +description = "ability modifier -> ability modifier for score 14 is +2" [8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2] -description = "ability modifier for score 15 is +2" +description = "ability modifier -> ability modifier for score 15 is +2" [c3ec871e-1791-44d0-b3cc-77e5fb4cd33d] -description = "ability modifier for score 16 is +3" +description = "ability modifier -> ability modifier for score 16 is +3" [3d053cee-2888-4616-b9fd-602a3b1efff4] -description = "ability modifier for score 17 is +3" +description = "ability modifier -> ability modifier for score 17 is +3" [bafd997a-e852-4e56-9f65-14b60261faee] -description = "ability modifier for score 18 is +4" +description = "ability modifier -> ability modifier for score 18 is +4" [4f28f19c-2e47-4453-a46a-c0d365259c14] description = "random ability is within range" @@ -58,3 +65,8 @@ description = "random character is valid" [2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe] description = "each ability is only calculated once" +include = false + +[dca2b2ec-f729-4551-84b9-078876bb4808] +description = "each ability is only calculated once" +reimplements = "2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe" diff --git a/exercises/practice/dnd-character/dnd_character_test.py b/exercises/practice/dnd-character/dnd_character_test.py index e506fdb117..0e4709863b 100644 --- a/exercises/practice/dnd-character/dnd_character_test.py +++ b/exercises/practice/dnd-character/dnd_character_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dnd-character/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest @@ -76,3 +76,8 @@ def test_random_character_is_valid(self): def test_each_ability_is_only_calculated_once(self): Char = Character() self.assertIs(Char.strength == Char.strength, True) + self.assertIs(Char.dexterity == Char.dexterity, True) + self.assertIs(Char.constitution == Char.constitution, True) + self.assertIs(Char.intelligence == Char.intelligence, True) + self.assertIs(Char.wisdom == Char.wisdom, True) + self.assertIs(Char.charisma == Char.charisma, True) From c9c67a864fa5fbc0dbc6e711d7ee7b0e41bdc5f8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:07:37 -0800 Subject: [PATCH 572/826] Synced exercise tests to problem specifications. (#3578) --- exercises/practice/knapsack/.meta/tests.toml | 18 +++++++++++++++--- exercises/practice/knapsack/knapsack_test.py | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/exercises/practice/knapsack/.meta/tests.toml b/exercises/practice/knapsack/.meta/tests.toml index 5a7805b017..8e013ef199 100644 --- a/exercises/practice/knapsack/.meta/tests.toml +++ b/exercises/practice/knapsack/.meta/tests.toml @@ -1,9 +1,21 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [a4d7d2f0-ad8a-460c-86f3-88ba709d41a7] description = "no items" +include = false + +[3993a824-c20e-493d-b3c9-ee8a7753ee59] +description = "no items" +reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7" [1d39e98c-6249-4a8b-912f-87cb12e506b0] description = "one item, too heavy" diff --git a/exercises/practice/knapsack/knapsack_test.py b/exercises/practice/knapsack/knapsack_test.py index 011f7ba832..946d0d5130 100644 --- a/exercises/practice/knapsack/knapsack_test.py +++ b/exercises/practice/knapsack/knapsack_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest From ca3f65e0adac14bf6a1ba6c0c60c950ece8dce7c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:07:59 -0800 Subject: [PATCH 573/826] Synced exercise tests to problem specifications. (#3579) --- exercises/practice/ledger/.meta/tests.toml | 5 ++++ exercises/practice/ledger/ledger_test.py | 34 +++++++++++----------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/exercises/practice/ledger/.meta/tests.toml b/exercises/practice/ledger/.meta/tests.toml index e71dfbfcaf..4ea45ceb12 100644 --- a/exercises/practice/ledger/.meta/tests.toml +++ b/exercises/practice/ledger/.meta/tests.toml @@ -20,6 +20,7 @@ description = "credit and debit" [502c4106-0371-4e7c-a7d8-9ce33f16ccb1] description = "multiple entries on same date ordered by description" +include = false [29dd3659-6c2d-4380-94a8-6d96086e28e1] description = "final order tie breaker is change" @@ -41,3 +42,7 @@ description = "Dutch negative number with 3 digits before decimal point" [29670d1c-56be-492a-9c5e-427e4b766309] description = "American negative number with 3 digits before decimal point" + +[9c70709f-cbbd-4b3b-b367-81d7c6101de4] +description = "multiple entries on same date ordered by description" +reimplements = "502c4106-0371-4e7c-a7d8-9ce33f16ccb1" diff --git a/exercises/practice/ledger/ledger_test.py b/exercises/practice/ledger/ledger_test.py index cc37167146..1fc6671cf8 100644 --- a/exercises/practice/ledger/ledger_test.py +++ b/exercises/practice/ledger/ledger_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/ledger/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest @@ -54,22 +54,6 @@ def test_credit_and_debit(self): ) self.assertEqual(format_entries(currency, locale, entries), expected) - def test_multiple_entries_on_same_date_ordered_by_description(self): - currency = "USD" - locale = "en_US" - entries = [ - create_entry("2015-01-02", "Get present", 1000), - create_entry("2015-01-01", "Buy present", -1000), - ] - expected = "\n".join( - [ - "Date | Description | Change ", - "01/01/2015 | Buy present | ($10.00)", - "01/02/2015 | Get present | $10.00 ", - ] - ) - self.assertEqual(format_entries(currency, locale, entries), expected) - def test_final_order_tie_breaker_is_change(self): currency = "USD" locale = "en_US" @@ -171,3 +155,19 @@ def test_american_negative_number_with_3_digits_before_decimal_point(self): ] ) self.assertEqual(format_entries(currency, locale, entries), expected) + + def test_multiple_entries_on_same_date_ordered_by_description(self): + currency = "USD" + locale = "en_US" + entries = [ + create_entry("2015-01-01", "Get present", 1000), + create_entry("2015-01-01", "Buy present", -1000), + ] + expected = "\n".join( + [ + "Date | Description | Change ", + "01/01/2015 | Buy present | ($10.00)", + "01/01/2015 | Get present | $10.00 ", + ] + ) + self.assertEqual(format_entries(currency, locale, entries), expected) From dfcb006ebad0dc76daef9b8b11c6e2b2bc6b7234 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:08:18 -0800 Subject: [PATCH 574/826] Synced exercise tests to problem specificatoins. (#3580) --- exercises/practice/poker/.meta/tests.toml | 16 ++++++++++++++++ exercises/practice/poker/poker_test.py | 16 +++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/exercises/practice/poker/.meta/tests.toml b/exercises/practice/poker/.meta/tests.toml index 76ac892d93..2e654ef63b 100644 --- a/exercises/practice/poker/.meta/tests.toml +++ b/exercises/practice/poker/.meta/tests.toml @@ -21,12 +21,18 @@ description = "a tie has multiple winners" [61ed83a9-cfaa-40a5-942a-51f52f0a8725] description = "multiple hands with the same high cards, tie compares next highest ranked, down to last card" +[da01becd-f5b0-4342-b7f3-1318191d0580] +description = "winning high card hand also has the lowest card" + [f7175a89-34ff-44de-b3d7-f6fd97d1fca4] description = "one pair beats high card" [e114fd41-a301-4111-a9e7-5a7f72a76561] description = "highest pair wins" +[b3acd3a7-f9fa-4647-85ab-e0a9e07d1365] +description = "both hands have the same pair, high card wins" + [935bb4dc-a622-4400-97fa-86e7d06b1f76] description = "two pairs beats one pair" @@ -53,6 +59,11 @@ description = "both hands have three of a kind, tie goes to highest ranked tripl [eb856cc2-481c-4b0d-9835-4d75d07a5d9d] description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +include = false + +[26a4a7d4-34a2-4f18-90b4-4a8dd35d2bb1] +description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +reimplements = "eb856cc2-481c-4b0d-9835-4d75d07a5d9d" [a858c5d9-2f28-48e7-9980-b7fa04060a60] description = "a straight beats three of a kind" @@ -77,6 +88,11 @@ description = "flush beats a straight" [4d90261d-251c-49bd-a468-896bf10133de] description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +include = false + +[e04137c5-c19a-4dfc-97a1-9dfe9baaa2ff] +description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +reimplements = "4d90261d-251c-49bd-a468-896bf10133de" [3a19361d-8974-455c-82e5-f7152f5dba7c] description = "full house beats a flush" diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py index c80aad6f05..b9a252012d 100644 --- a/exercises/practice/poker/poker_test.py +++ b/exercises/practice/poker/poker_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/poker/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest @@ -39,6 +39,11 @@ def test_multiple_hands_with_the_same_high_cards_tie_compares_next_highest_ranke best_hands(["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"]), ["3S 5H 6S 8D 7H"] ) + def test_winning_high_card_hand_also_has_the_lowest_card(self): + self.assertEqual( + best_hands(["2S 5H 6S 8D 7H", "3S 4D 6D 8C 7S"]), ["2S 5H 6S 8D 7H"] + ) + def test_one_pair_beats_high_card(self): self.assertEqual( best_hands(["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"]), ["2S 4H 6S 4D JH"] @@ -49,6 +54,11 @@ def test_highest_pair_wins(self): best_hands(["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"]), ["2S 4H 6C 4D JD"] ) + def test_both_hands_have_the_same_pair_high_card_wins(self): + self.assertEqual( + best_hands(["4H 4S AH JC 3D", "4C 4D AS 5D 6C"]), ["4H 4S AH JC 3D"] + ) + def test_two_pairs_beats_one_pair(self): self.assertEqual( best_hands(["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"]), ["4S 5H 4C 8C 5C"] @@ -99,7 +109,7 @@ def test_with_multiple_decks_two_players_can_have_same_three_of_a_kind_ties_go_t self, ): self.assertEqual( - best_hands(["4S AH AS 7C AD", "4S AH AS 8C AD"]), ["4S AH AS 8C AD"] + best_hands(["5S AH AS 7C AD", "4S AH AS 8C AD"]), ["4S AH AS 8C AD"] ) def test_a_straight_beats_three_of_a_kind(self): @@ -143,7 +153,7 @@ def test_both_hands_have_a_flush_tie_goes_to_high_card_down_to_the_last_one_if_n self, ): self.assertEqual( - best_hands(["4H 7H 8H 9H 6H", "2S 4S 5S 6S 7S"]), ["4H 7H 8H 9H 6H"] + best_hands(["2H 7H 8H 9H 6H", "3S 5S 6S 7S 8S"]), ["2H 7H 8H 9H 6H"] ) def test_full_house_beats_a_flush(self): From 185fbdfac9d6961c355aaeb428ee92f1c3cc25f3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 06:02:10 -0800 Subject: [PATCH 575/826] Random grammar, spelling and content edits. (#3581) [no important files changed] --- CONTRIBUTING.md | 8 +- README.md | 6 +- bin/generate_tests.py | 2 +- concepts/binary-octal-hexadecimal/about.md | 7 +- .../binary-octal-hexadecimal/introduction.md | 2 +- concepts/classes/links.json | 2 +- concepts/conditionals/introduction.md | 8 +- concepts/function-arguments/about.md | 2 +- concepts/function-arguments/introduction.md | 2 +- concepts/functions/about.md | 6 +- concepts/functools/about.md | 21 +- concepts/functools/introduction.md | 2 +- docs/TRACEBACKS.md | 4 +- .../concept/ellens-alien-game/classes_test.py | 2 +- .../locomotive_engineer.py | 4 +- reference/concept-exercise-mapping.md | 4 +- reference/track_exercises_overview.md | 242 +++++++++--------- 17 files changed, 161 insertions(+), 163 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0bac23e0af..bc278b027e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -170,7 +170,7 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer - Favor `f-strings` for dynamic failure messages. Please make your error messages as relevant and human-readable as possible. - We relate test cases to **task number** via a custom [PyTest Marker][pytestmark]. - These take the form of `@pytest.mark.task(taskno=)`. See [Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile] for an example. -- We prefer **test data files** when test inputs/ouputs are verbose. +- We prefer **test data files** when test inputs/outputs are verbose. - These should be named with `_data` or `_test_data` at the end of the filename, and saved alongside the test case file. - See the [Cater-Waiter][cater-waiter] exercise directory for an example of this setup. - **Test data files** need to be added under an `editor` key within [`config.json "files"`][exercise-config-json]. @@ -270,9 +270,9 @@ Although the majority of test cases are written using `unittest.TestCase`, - [ ] `.meta/config.json` (**required**) - [ ] `.meta/example.py` (**required**) - [ ] `.meta/design.md` (_optional_) - - [ ] `.meta/template.j2` (_template for generating tests from cannonical data_) - - [ ] `.meta/tests.toml` (_tests configuration from cannonical data_) - - [ ] `_test.py` (_**auto-generated from cannonical data**_) + - [ ] `.meta/template.j2` (_template for generating tests from canonical data_) + - [ ] `.meta/tests.toml` (_tests configuration from canonical data_) + - [ ] `_test.py` (_**auto-generated from canonical data**_) - [ ] `.py` (**required**) diff --git a/README.md b/README.md index 1c82f33b9d..99357745f0 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ Hi.  👋🏽  👋  **We are happy you are here.**  🎉&nb **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. -🌟   Track exercises support Python `3.7` - `3.11.2`. +🌟   Track exercises support Python `3.7` - `3.11.5`. Exceptions to this support are noted where they occur. -🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`. +🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.5`. Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴 . Concept exercises are constrained to a small set of language or syntax features. @@ -67,7 +67,7 @@ _Thoughtful suggestions will likely result faster & more enthusiastic responses ## Python Software and Documentation -**Copyright © 2001-2022 Python Software Foundation. All rights reserved.** +**Copyright © 2001-2023 Python Software Foundation. All rights reserved.** Python software and documentation are licensed under the [PSF License Agreement][psf-license]. diff --git a/bin/generate_tests.py b/bin/generate_tests.py index 6c9b1c38b9..a3089eb8b4 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -138,7 +138,7 @@ def parse_datetime(string: str, strip_module: bool = False) -> datetime: ]| # OR o(?:[0-8]{1,3}) # an octal value | # OR - x(?:[0-9A-Fa-f]{2}) # a hexidecimal value + x(?:[0-9A-Fa-f]{2}) # a hexadecimal value | # OR N # a unicode char name composed of \{ # an opening brace diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md index 667f3eb6cd..a7fca3714e 100644 --- a/concepts/binary-octal-hexadecimal/about.md +++ b/concepts/binary-octal-hexadecimal/about.md @@ -190,8 +190,8 @@ As with binary and octal, Python will automatically convert hexadecimal literals 291 ``` -As with binary and octal - hexidecimal literals **are ints**, and you can perform all integer operations. -Prefixing a non-hexidecimal number with `0x` will raise a `SyntaxError`. +As with binary and octal - hexadecimal literals **are ints**, and you can perform all integer operations. +Prefixing a non-hexadecimal number with `0x` will raise a `SyntaxError`. ### Converting to and from Hexadecimal Representation @@ -216,9 +216,6 @@ As with binary and octal, giving the wrong base will raise a `ValueError`. [binary]: https://en.wikipedia.org/wiki/Binary_number [bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count [bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length -[bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count -[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length [hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal -[methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types [numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system [octal]: https://en.wikipedia.org/wiki/Octal diff --git a/concepts/binary-octal-hexadecimal/introduction.md b/concepts/binary-octal-hexadecimal/introduction.md index 820aac33ee..a06ac922fa 100644 --- a/concepts/binary-octal-hexadecimal/introduction.md +++ b/concepts/binary-octal-hexadecimal/introduction.md @@ -5,7 +5,7 @@ Binary is base 2, octal is base 8, and hexadecimal is base 16. Normal integers are base 10 in python. Binary, octal, and hexadecimal literals are all considered `int` subtypes and Python automatically converts between them. This means that they can only represent zero, positive, and negative numbers that do not have a fractional or decimal part. -Binary, octal, and hexidecimal numbers support all integer operations. +Binary, octal, and hexadecimal numbers support all integer operations. However, division (_as with ints_) will return a `float`. [numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system diff --git a/concepts/classes/links.json b/concepts/classes/links.json index 5687b92a3d..8cc9ba5926 100644 --- a/concepts/classes/links.json +++ b/concepts/classes/links.json @@ -17,7 +17,7 @@ }, { "url": "https://dbader.org/blog/6-things-youre-missing-out-on-by-never-using-classes-in-your-python-code", - "description": "6 Things Youre Missing out on by never using classes in your Python code." + "description": "6 Things You are Missing out on by never using classes in your Python code." }, { "url": "http://python-history.blogspot.com/2010/06/inside-story-on-new-style-classes.html", diff --git a/concepts/conditionals/introduction.md b/concepts/conditionals/introduction.md index 29e3e63597..77bf9608ed 100644 --- a/concepts/conditionals/introduction.md +++ b/concepts/conditionals/introduction.md @@ -56,7 +56,7 @@ else: >>> z is greater than x and y ``` -[Boolen operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: +[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: ```python @@ -77,8 +77,8 @@ else: '13' ``` -[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement -[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools -[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing [boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not [comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons +[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools +[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/concepts/function-arguments/about.md b/concepts/function-arguments/about.md index f4a9c2b3f2..0f2ab5dddd 100644 --- a/concepts/function-arguments/about.md +++ b/concepts/function-arguments/about.md @@ -4,7 +4,7 @@ For the basics on function arguments, please see the [function concept][function ## Parameter Names -Paramater names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. +Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. Parameter names should not contain spaces or punctuation. ## Positional Arguments diff --git a/concepts/function-arguments/introduction.md b/concepts/function-arguments/introduction.md index 171675ce3c..07b885f332 100644 --- a/concepts/function-arguments/introduction.md +++ b/concepts/function-arguments/introduction.md @@ -4,7 +4,7 @@ For the basics on function arguments, please see the [function concept][function ## Parameter Names -Paramater names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. +Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. Parameter names should not contain spaces or punctuation. ## Positional Arguments diff --git a/concepts/functions/about.md b/concepts/functions/about.md index 9d8fddfa95..f3630af763 100644 --- a/concepts/functions/about.md +++ b/concepts/functions/about.md @@ -2,11 +2,11 @@ A [`function`][function] is a block of organized, reusable code that is used to perform a specific task. `Functions` provide better modularity for your application and a high degree of code reuse. -Python, like other programming languages, has [_built-in functions_][build-in functions] ([`print`][print], [`map`][map], and so on) that are readily available. +Python, like other programming languages, has [_built-in functions_][built-in functions] ([`print`][print], [`map`][map], and so on) that are readily available. You can also define your own functions. Those are called [`user-defined functions`][user defined functions]. Functions can run something as simple as _printing a message to the console_ or they can be quite complex. -To execute the code inside a function, you need to call the function, which is done by using the function name followed by parenthesese [`()`]. +To execute the code inside a function, you need to call the function, which is done by using the function name followed by parentheses [`()`]. Data, known as [`arguments`][arguments], can be passed to the function by placing them inside the parenthesese. A function definition may include zero or more [`parameters`][parameters]. Parameters define what argument(s) the function accepts. @@ -376,7 +376,7 @@ The full list of function attributes can be found at [Python DataModel][attribut [LEGB Rule]: https://realpython.com/python-scope-legb-rule/ [arguments]: https://www.w3schools.com/python/gloss_python_function_arguments.asp [attributes]: https://docs.python.org/3/reference/datamodel.html#index-33 -[build-in functions]: https://docs.python.org/3/library/functions.html +[built-in functions]: https://docs.python.org/3/library/functions.html [def]: https://www.geeksforgeeks.org/python-def-keyword/ [dict]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [first class objects]: https://en.wikipedia.org/wiki/First-class_object diff --git a/concepts/functools/about.md b/concepts/functools/about.md index 32748a45c2..e5afb577d3 100644 --- a/concepts/functools/about.md +++ b/concepts/functools/about.md @@ -12,7 +12,7 @@ The functools module is for higher-order functions: functions that act on or ret Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable. -Here ```maxsize = 128``` means that it is going to memoize lastest 128 function calls at max. +Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max. The lru_cache works the same way but it can cache at max maxsize calls and if type = True, then the function arguments of different types will be cached separately i.e. 5 and 5.0 will be cached differently. @@ -23,8 +23,8 @@ The lru_cache works the same way but it can cache at max maxsize calls and if ty ```python >>> @cache - def factorial(n): - return n * factorial(n-1) if n else 1 +>>> def factorial(n): +>>> return n * factorial(n-1) if n else 1 >>> factorial(10) # no previously cached result, makes 11 recursive calls 3628800 @@ -37,9 +37,10 @@ The lru_cache works the same way but it can cache at max maxsize calls and if ty # Some types such as str and int may be cached separately even when typed is false. -@lru_cache(maxsize = 128) - def factorial(n): - return n * factorial(n-1) if n else 1 +>>> @lru_cache(maxsize = 128) +>>> def factorial(n): +>>> return n * factorial(n-1) if n else 1 + >>> factorial(10) 3628800 @@ -50,7 +51,7 @@ CacheInfo(hits=0, misses=11, maxsize=128, currsize=11) ## Generic functions -***[Generic functions](https://pymotw.com/3/functools/#generic-functions)*** are those which preform the operation based on the argument given to them. In statically typed languages it can be done by function overloading. +***[Generic functions](https://pymotw.com/3/functools/#generic-functions)*** are those which perform the operation based on the argument given to them. In statically typed languages it can be done by function overloading. In python functools provides the `singledispatch()` decorator to register a set of generic functions for automatic switching based on the type of the first argument to a function. @@ -193,11 +194,11 @@ True The ```pow_2.func``` is same as the ```pow``` function. -Here ```pow_2.args``` return an empty tuple becuse we does not pass any positional argument to out partial object call. +Here ```pow_2.args``` returns an empty tuple because we do not pass any positional argument to our partial object call. -```pow_2.keywords``` return a dictionary of keywords argument which will be supplied when the partial object is called. +```pow_2.keywords``` returns a dictionary of keywords argument which will be supplied when the partial object is called. -Here ```two_pow.args``` return an ```(2,)``` tuple because we passed 2 as an argument while creating the pratial object, which fixed the value of ```base``` argument as ```2```. +Here ```two_pow.args``` returns a ```(2,)``` tuple because we passed 2 as an argument while creating the partial object, which fixed the value of ```base``` argument as ```2```. ### ```partialmethod``` diff --git a/concepts/functools/introduction.md b/concepts/functools/introduction.md index 15e83e3e61..c91aedc81b 100644 --- a/concepts/functools/introduction.md +++ b/concepts/functools/introduction.md @@ -12,7 +12,7 @@ The functools module is for higher-order functions: functions that act on or ret Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable. -Here ```maxsize = 128``` means that it is going to memoize lastest 128 function calls at max. +Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max. ### ```@functools.cache(user_function)``` diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 5914429db9..b7a4b010b9 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -415,7 +415,7 @@ def halve_and_quadruple(num): print((num / 2) * 4) return (num / 2) * 4 -What the `print` calls revealed is that we used `/` when we should have used `//`, the [floor divison operator][floor divison operator]. +What the `print` calls revealed is that we used `/` when we should have used `//`, the [floor division operator][floor division operator]. ## Logging @@ -487,7 +487,7 @@ AssertionError: divisor must not be 0 ``` -If we start reading the Traceback at the bottom (as we should) we quickly see the problem is that `0` should not be passsed as the `divisor`. +If we start reading the Traceback at the bottom (as we should) we quickly see the problem is that `0` should not be passed as the `divisor`. `assert` can also be used to test that a value is of the expected type: diff --git a/exercises/concept/ellens-alien-game/classes_test.py b/exercises/concept/ellens-alien-game/classes_test.py index 58b78d7096..3d2b986be4 100644 --- a/exercises/concept/ellens-alien-game/classes_test.py +++ b/exercises/concept/ellens-alien-game/classes_test.py @@ -23,7 +23,7 @@ class ClassesTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_alien_has_correct_initial_coordinates(self): - """Test thst the Alien class gets correctly initialised.""" + """Test that the Alien class gets correctly initialised.""" alien = Alien(2, -1) error_message = (f'Created a new Alien by calling Alien(2, -1). ' diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer.py b/exercises/concept/locomotive-engineer/locomotive_engineer.py index cea48cc028..180329208c 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer.py @@ -13,8 +13,8 @@ def get_list_of_wagons(): def fix_list_of_wagons(each_wagons_id, missing_wagons): """Fix the list of wagons. - :parm each_wagons_id: list - the list of wagons. - :parm missing_wagons: list - the list of missing wagons. + :param each_wagons_id: list - the list of wagons. + :param missing_wagons: list - the list of missing wagons. :return: list - list of wagons. """ pass diff --git a/reference/concept-exercise-mapping.md b/reference/concept-exercise-mapping.md index 1bda69ad12..1b3e8a1a45 100644 --- a/reference/concept-exercise-mapping.md +++ b/reference/concept-exercise-mapping.md @@ -398,14 +398,14 @@ _Concepts needed for a deeper understanding/fluency_ ## Specialized -_(These are probably outside scope of an Exercism Concept exercise, but might make good longer/practice exercises that recieve mentoring)_ +_(These are probably outside scope of an Exercism Concept exercise, but might make good longer/practice exercises that receive mentoring)_
Advanced/Specialized Concepts
-- [ ] Asynchronous operatons +- [ ] Asynchronous operations - [ ] [`async`][keyword-async] - [ ] [`await`][keyword-await] diff --git a/reference/track_exercises_overview.md b/reference/track_exercises_overview.md index 761f2c4764..6d09504071 100644 --- a/reference/track_exercises_overview.md +++ b/reference/track_exercises_overview.md @@ -14,125 +14,125 @@ | Exercise | Difficulty | Solutions | Prereqs | Practices | Hints? | Approaches? | Mentor Notes | Appends? | Jinja? | |-------------------------------------------------------------------------------------------------------------------------------------------- |:----------: |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------- |-------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------ |---------------------------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------- | | [**Hello World**](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.docs/instructions.md) | 1 | NA | NONE | NONE | NA | NA | NA | NA | NA | -| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/template.j2) | -| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/template.j2) | -| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/template.j2) | -| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/template.j2) | -| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/template.j2) | -| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/template.j2) | -| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/armstrong-numbers/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/template.j2) | -| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/template.j2) | -| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/template.j2) | -| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/template.j2) | -| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/template.j2) | -| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/template.j2) | -| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/template.j2) | +| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/template.j2) | +| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/template.j2) | +| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/template.j2) | +| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/template.j2) | +| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/template.j2) | +| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/template.j2) | +| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/armstrong-numbers/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/template.j2) | +| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/template.j2) | +| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/template.j2) | +| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/template.j2) | +| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/template.j2) | +| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/template.j2) | +| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/template.j2) | | [Bottle Song](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bottle-song/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json/#LC1012) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC1012) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/template.j2) | -| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/template.j2) | -| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/change/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/template.j2) | -| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/template.j2) | -| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/template.j2) | -| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/template.j2) | -| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/template.j2) | -| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/template.j2) | -| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/template.j2) | -| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/darts/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/template.j2) | -| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/template.j2) | -| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/template.j2) | -| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/dnd-character/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/template.j2) | -| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/template.j2) | -| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.append.md) | | -| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/template.j2) | -| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/template.j2) | -| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/template.j2) | -| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/template.j2) | -| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L450) | NONE | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/gigasecond/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/template.j2) | -| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/template.j2) | -| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/template.j2) | -| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grains/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/template.j2) | -| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/template.j2) | -| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/template.j2) | -| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.append.md) | | -| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/template.j2) | -| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/house/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/template.j2) | -| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isbn-verifier/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/template.j2) | -| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/template.j2) | -| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/killer-sudodu-helper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1385) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1384) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/template.j2) | -| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/template.j2) | -| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/template.j2) | -| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/template.j2) | -| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/template.j2) | -| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/template.j2) | -| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/template.j2) | -| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/template.j2) | -| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/template.j2) | -| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1418) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1417) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/template.j2) | -| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/template.j2) | -| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/template.j2) | -| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/template.j2) | -| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/template.j2) | -| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/template.j2) | -| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/template.j2) | -| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | | | | | -| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/palindrome-products/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/template.j2) | -| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/pangram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/template.j2) | -| [Pascals Triangle](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pascals-triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1300) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1299) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/hints.md) | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/template.j2) | -| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/perfect-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/template.j2) | -| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/phone-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/template.j2) | -| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/template.j2) | -| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/template.j2) | -| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 9 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/template.j2) | -| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/template.j2) | -| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/template.j2) | -| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC661) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L661) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/template.j2) | -| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/template.j2) | -| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/template.j2) | -| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/template.j2) | -| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/template.j2) | -| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/template.j2) | -| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/react/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | | | | | -| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/template.j2) | -| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC631) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L631) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/template.j2) | -| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/template.j2) | -| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/template.j2) | -| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 8 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/template.j2) | -| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/template.j2) | -| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/rna-transcription/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/template.j2) | -| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/robot-name/) | | | -| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/template.j2) | -| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/template.j2) | -| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/template.j2) | -| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/template.j2) | -| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/saddle-points/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/template.j2) | -| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/template.j2) | -| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/say/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/template.j2) | -| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/template.j2) | -| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/template.j2) | -| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/template.j2) | -| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/series/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/series/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/template.j2) | -| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/template.j2) | -| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/template.j2) | -| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/template.j2) | -| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/hints.md) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.append.md) | | -| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/space-age/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/template.j2) | -| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/template.j2) | -| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/square-root/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L609) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L608) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/template.j2) | -| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/template.j2) | -| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/template.j2) | -| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/template.j2) | -| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/template.j2) | -| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.append.md) | | -| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/triangle/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/template.j2) | -| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/hints.md) | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/template.j2) | -| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/template.j2) | -| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/template.j2) | -| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/template.j2) | -| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/template.j2) | -| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/template.j2) | -| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/template.j2) | -| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/template.j2) | -| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/template.j2) | -| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/template.j2) | +| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/template.j2) | +| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/change/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/template.j2) | +| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/template.j2) | +| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/template.j2) | +| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/template.j2) | +| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/template.j2) | +| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/template.j2) | +| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/template.j2) | +| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/darts/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/template.j2) | +| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/template.j2) | +| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/template.j2) | +| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/dnd-character/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/template.j2) | +| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/template.j2) | +| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.append.md) | | +| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/template.j2) | +| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/template.j2) | +| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/template.j2) | +| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/template.j2) | +| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L450) | NONE | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/gigasecond/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/template.j2) | +| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/template.j2) | +| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/template.j2) | +| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grains/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/template.j2) | +| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/template.j2) | +| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/template.j2) | +| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.append.md) | | +| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/template.j2) | +| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/house/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/template.j2) | +| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isbn-verifier/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/template.j2) | +| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/template.j2) | +| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/killer-sudodu-helper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1385) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1384) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/template.j2) | +| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/template.j2) | +| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/template.j2) | +| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/template.j2) | +| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/template.j2) | +| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/template.j2) | +| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/template.j2) | +| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/template.j2) | +| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/template.j2) | +| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1418) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1417) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/template.j2) | +| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/template.j2) | +| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/template.j2) | +| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/template.j2) | +| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/template.j2) | +| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/template.j2) | +| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/template.j2) | +| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | | | | | +| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/palindrome-products/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/template.j2) | +| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/pangram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/template.j2) | +| [Pascals Triangle](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pascals-triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1300) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1299) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/hints.md) | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/template.j2) | +| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/perfect-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/template.j2) | +| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/phone-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/template.j2) | +| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/template.j2) | +| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/template.j2) | +| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 9 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/template.j2) | +| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/template.j2) | +| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/template.j2) | +| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC661) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L661) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/template.j2) | +| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/template.j2) | +| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/template.j2) | +| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/template.j2) | +| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/template.j2) | +| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/template.j2) | +| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/react/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | | | | | +| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/template.j2) | +| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC631) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L631) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/template.j2) | +| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/template.j2) | +| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/template.j2) | +| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 8 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/template.j2) | +| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/template.j2) | +| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/rna-transcription/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/template.j2) | +| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/robot-name/) | | | +| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/template.j2) | +| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/template.j2) | +| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/template.j2) | +| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/template.j2) | +| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/saddle-points/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/template.j2) | +| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/template.j2) | +| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/say/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/template.j2) | +| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/template.j2) | +| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/template.j2) | +| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/template.j2) | +| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/series/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/series/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/template.j2) | +| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/template.j2) | +| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/template.j2) | +| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/template.j2) | +| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/hints.md) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.append.md) | | +| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/space-age/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/template.j2) | +| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/template.j2) | +| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/square-root/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L609) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L608) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/template.j2) | +| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/template.j2) | +| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/template.j2) | +| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/template.j2) | +| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/template.j2) | +| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.append.md) | | +| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/triangle/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/template.j2) | +| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/hints.md) | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/template.j2) | +| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/template.j2) | +| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/template.j2) | +| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/template.j2) | +| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/template.j2) | +| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/template.j2) | +| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/template.j2) | +| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/template.j2) | +| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/template.j2) | +| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/template.j2) |
@@ -217,8 +217,8 @@ | | [complex-numbers](https://github.com/exercism/python/blob/main/concepts/complex-numbers) | | ~ | [#2208](https://github.com/exercism/v3/issues/2208) | TBD | | | [conditionals](https://github.com/exercism/python/blob/main/concepts/conditionals) | | [Meltdown Mitigation ](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation/.meta) | Full | | | [comparisons](https://github.com/exercism/python/blob/main/concepts/comparisons) | | [Black Jack](https://github.com/exercism/python/tree/main/exercises/concept/black-jack) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/black-jack/.meta) | Full | -| | [strings](https://github.com/exercism/python/blob/main/concepts/strings) | | [Litte Sister's Vocab](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab/.meta) | Full | -| | [string-methods](https://github.com/exercism/python/blob/main/concepts/string-methods) | | [Litte Sister's Essay](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay/.meta) | Full | +| | [strings](https://github.com/exercism/python/blob/main/concepts/strings) | | [Little Sister's Vocab](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab/.meta) | Full | +| | [string-methods](https://github.com/exercism/python/blob/main/concepts/string-methods) | | [Little Sister's Essay](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay/.meta) | Full | | | [string-formatting](https://github.com/exercism/python/blob/main/concepts/string-formatting) | | TBD (_rewrite_) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet/.meta) | Full | | | [lists](https://github.com/exercism/python/blob/main/concepts/lists) | | [Card Games](https://github.com/exercism/python/tree/main/exercises/concept/card-games) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/card-games/.meta) | Full | | | [list-methods](https://github.com/exercism/python/blob/main/concepts/list-methods) | | [Chaitanas Colossal Coaster](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster/.meta) | Full | @@ -359,7 +359,7 @@ type-annotation(("TBD-F
Type Annotation
(type hints)
")):::TB type-aliases((TBD-F
Type Aliases)):::TBD-F unicode-data((TBD-F
unicode data)):::TBD-F unpacking-and-multiple-assignment((Locomotive Engineer
Unpacking &
Multi-assignment
)):::Beta -user-defined-errors((TBD
User Definied Errors)):::TBD +user-defined-errors((TBD
User Defined Errors)):::TBD walrus-operator((TBD-F
Walrus Operator)):::TBD-F with(("TBD
with
(Context Managers)
")):::TBD unittest(("TBD-F
unittest
(testing)
")):::TBD-F From 7e5a83d333424c615b9a4886fc11ce23d4d1c1e0 Mon Sep 17 00:00:00 2001 From: colinleach Date: Wed, 27 Dec 2023 16:02:33 -0700 Subject: [PATCH 576/826] The `bitwise-operators` concept (#3564) * bitwise-operators concept * small fizes to about.md * Proposed Edits and Links First draft of edits and links for Bitwise operations. * Typos and Review Changes --------- Co-authored-by: BethanyG --- concepts/bitwise-operators/.meta/config.json | 4 +- concepts/bitwise-operators/about.md | 197 ++++++++++++++++++- concepts/bitwise-operators/introduction.md | 21 +- concepts/bitwise-operators/links.json | 20 +- 4 files changed, 230 insertions(+), 12 deletions(-) diff --git a/concepts/bitwise-operators/.meta/config.json b/concepts/bitwise-operators/.meta/config.json index 9b9e8da5a9..7767ff5d74 100644 --- a/concepts/bitwise-operators/.meta/config.json +++ b/concepts/bitwise-operators/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "Python supports bitwise operations such as left/right shift, and, or, xor, and not.", + "authors": ["BethanyG", "colinleach"], "contributors": [] } diff --git a/concepts/bitwise-operators/about.md b/concepts/bitwise-operators/about.md index c628150d56..a4ddb509c1 100644 --- a/concepts/bitwise-operators/about.md +++ b/concepts/bitwise-operators/about.md @@ -1,2 +1,197 @@ -#TODO: Add about for this concept. +# About +Down at the hardware level, transistors can only be on or off: two states that we traditionally represent with `1` and `0`. +These are the [`binary digits`][binary-digits], abbreviated as [`bits`][bits]. +Awareness of `bits` and `binary` is particularly important for systems programmers working in low-level languages. + +However, for most of the history of computing the programming priority has been to find increasingly sophisticated ways to _abstract away_ this binary reality. +In Python (and many other [high-level programming languages][high-level-language]), we work with `int`, `float`, `string` and other defined _types_, up to and including audio and video formats. +We let the Python internals take care of (eventually) translating everything to bits. + +Nevertheless, using [bitwise-operators][python-bitwise-operators] and [bitwise operations][python-bitwise-operations] can sometimes have significant advantages in speed and memory efficiency, even in a high-level language like Python. + + +## Entering and Displaying Binary Numbers + +Unsurprisingly, Python interacts with the user using decimal numbers, but a programmer can override this default. +In fact, Python will readily accept an `int` in `binary`, `hexadecimal`, or `octal` format, and will happily perform mathematical operations between them. +For more details, you can review the [concept:python/binary-octal-hexadecimal]() concept. + +Binary numbers are entered with a `0b` prefix, just as `0x` can be used for hexadecimal (_hex numbers are a concise way to represent groups of 4 bits_), and `oct` can be used for octal numbers. + +There are multiple ways to convert integers to binary strings, varying in whether they include the `0b` prefix and whether they support left-padding with zeros: + + +```python +# Binary entry. +>>> 0b10111 +23 + +# Converting an int display to binary string, with prefix. +>>> bin(23) +'0b10111' + +>>> number = 23 + +# Binary without prefix, padded to 8 digits. +>>> format(number, '08b') +'00010111' + +# Same format, but using an f-string. +>>> f"{number} in decimal is {number:08b} in binary and {number:x} in hex" +'23 in decimal is 00010111 in binary and 17 in hex' +``` + + +## [`Bitwise Logic`][python-bitwise-operations] + +In the [concept:python/bools]() concept, we discussed the _logical operators_ `and`, `or` and `not` used with Boolean (_`True` and `False`_) values. +The same logic rules apply when working with bits. + +However, the bitwise equivalents of the logical operators `&` (_and_), `|` (_or_), `~` (_not_), and `^` (_[XOR][xor]_), are applied to each _bit_ in a binary representation, treating `1` as `True` ("on") and `0` as `False` ("off"). +An example with the bitwise `&` might make this clearer: + + +```python +>>> x = 0b01100110 +>>> y = 0b00101010 + +>>> format(x & y, '08b') +'00100010' +``` + +Only positions with a `1` in _**both**_ the input numbers are set to `1` in the output. + +Bitwise `&` is commonly used as a way to isolate single bits in a compacted set of `True`/`False` values, such as user-configurable settings in an app. +This enables the value of individual bits to control program logic: + + +```python +>>> number = 0b0110 +>>> number & 0b0001 > 0 +False + +>>> number & 0b0010 > 0 +True +``` + + +For a bitwise `|` (or), a `1` is set in the output if there is a `1` in _**either**_ of the inputs: + + +```python +>>> x = 0b01100110 +>>> y = 0b00101010 + +>>> format(x | y, '08b') +'01101110' +``` + + +With the `^` operator for bitwise e**x**clusive **or** (xor), a `1` is set if it appears in _**either**_ of the inputs _**but not both**_ inputs. +This symbol might seem familiar from the [concept:python/sets]() concept, where it is used for `set` _symmetric difference_, which is the same as [xor applied to sets][symmetric-difference]. +If xor `^` seems strange, be aware that this is by far the [most common operation in cryptography][xor-cipher]. + + +```python +>>> x = 0b01100110 +>>> y = 0b00101010 + +>>> format(x ^ y, '08b') +'01001100' +``` + + +Finally, there is the `~` operator (_the [tilde][tilde] character_), which is a bitwise `not` that takes a single input and _**inverts all the bits**_, which might not be the result you were expecting! +Each `1` in the representation changes to `0`, and vice versa. +See the section below for details. + + +## Negative Numbers and Binary Representation + +In decimal representation, we distinguish positive and negative numbers by using a `+` or `-` sign to the left of the digits. +Using these symbols at a binary level proved inefficient for digital computing and raised the problem that `+0` is not the same as `-0`. + +Rather than using `-` and `+`, all modern computers use a [`twos-complement`][twos-complement] representation for negative numbers, right down to the silicon chip level. +This means that all bits are inverted and a number is _**interpreted as negative**_ if the left-most bit (also termed the "most significant bit", or MSB) is a `1`. +Positive numbers have an MSB of `0`. +This representation has the advantage of only having one version of zero, so that the programmer doesn't have to manage `-0` and `+0`. + +This way of representing negative and positive numbers adds a complication for Python: there are no finite-integer concepts like `int32` or `int64` internally in the core langauge. +In 'modern' Python, `int`s are of unlimited size (_limited only by hardware capacity_), and a negative or bit-inverted number has a (_theoretically_) infinite number of `1`'s to the left, just as a positive number has unlimited `0`'s. + +This makes it difficult to give a useful example of `bitwise not`: + +```python +>>> x = 0b01100110 +>>> format(x, '08b') +'01100110' + +# This is a negative binary (not twos-complement display). +>>> format(~x, '08b') +'-1100111' + + # Decimal representation. +>>> x +102 + +# Using the Bitwise not, with an unintuitive result. +>>> ~x +-103 +``` + +This is **not** the `0b10011001` we would see in languages with fixed-size integers. + +The `~` operator only works as expected with _**unsigned**_ byte or integer types, or with fixed-sized integer types. +These numeric types are supported in third-party packages such as [`NumPy`][numpy], [`pandas`][pandas], and [`sympy`][sympy] but not in core Python. + +In practice, Python programmers quite often use the shift operators described below and `& | ^` with positive numbers only. +Bitwise operations with negative numbers are much less common. +One technique is to add [`2**32 (or 1 << 32)`][unsigned-int-python] to a negative value to make an `int` unsigned, but this gets difficult to manage. +Another strategy is to work with the [`ctypes`][ctypes-module] module, and use c-style integer types, but this is equally unwieldy. + + +## [`Shift operators`][bitwise-shift-operators] + +The left-shift operator `x << y` simply moves all the bits in `x` by `y` places to the left, filling the new gaps with zeros. +Note that this is arithmetically identical to multiplying a number by `2**y`. + +The right-shift operator `x >> y` does the opposite. +This is arithmetically identical to integer division `x // 2**y`. + +Keep in mind the previous section on negative numbers and their pitfalls when shifting. + + +```python +>>> x = 8 +>>> format(x, '08b') +'00001000' + +# A left bit shift. +>>> x << 2 +32 + +>>> format(x << 2, '08b') +'00100000' + +# A right bit shift. +>>> format(x >> 2, '08b') +'00000010' +``` + +[binary-digits]: https://www.khanacademy.org/computing/computers-and-internet/xcae6f4a7ff015e7d:digital-information/xcae6f4a7ff015e7d:binary-numbers/v/the-binary-number-system +[bits]: https://en.wikipedia.org/wiki/Bit +[bitwise-shift-operators]: https://docs.python.org/3/reference/expressions.html#shifting-operations +[ctypes-module]: https://docs.python.org/3/library/ctypes.html#module-ctypes +[high-level-language]: https://en.wikipedia.org/wiki/High-level_programming_language +[numpy]: https://numpy.org/doc/stable/user/basics.types.html +[pandas]: https://pandas.pydata.org/docs/reference/arrays.html#nullable-integer +[python-bitwise-operations]: https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations +[python-bitwise-operators]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations +[symmetric-difference]: https://math.stackexchange.com/questions/84184/relation-between-xor-and-symmetric-difference#:~:text=It%20is%20the%20same%20thing,they%20are%20indeed%20the%20same. +[sympy]: https://docs.sympy.org/latest/modules/codegen.html#predefined-types +[tilde]: https://en.wikipedia.org/wiki/Tilde +[twos-complement]: https://en.wikipedia.org/wiki/Two%27s_complement#:~:text=Two's%20complement%20is%20the%20most,number%20is%20positive%20or%20negative. +[unsigned-int-python]: https://stackoverflow.com/a/20768199 +[xor-cipher]: https://en.wikipedia.org/wiki/XOR_cipher +[xor]: https://stackoverflow.com/a/2451393 diff --git a/concepts/bitwise-operators/introduction.md b/concepts/bitwise-operators/introduction.md index bbe12ffd5e..88aba3a6a7 100644 --- a/concepts/bitwise-operators/introduction.md +++ b/concepts/bitwise-operators/introduction.md @@ -1 +1,20 @@ -#TODO: Add introduction for this concept. +# Introduction + +Down at the hardware level, transistors can only be on or off: two states that we traditionally represent with `1` and `0`. +These are the [`binary digits`][binary-digits], abbreviated as [`bits`][bits]. +Awareness of `bits` and `binary` is particularly important for systems programmers working in low-level languages. + +However, for most of the history of computing the programming priority has been to find increasingly sophisticated ways to _abstract away_ this binary reality. + + +In Python (and many other [high-level programming languages][high-level-language]), we work with `int`, `float`, `string` and other defined _types_, up to and including audio and video formats. +We let the Python internals take care of (eventually) translating everything to bits. + + +Nevertheless, using [bitwise-operators][python-bitwise-operators] and [bitwise operations][python-bitwise-operations] can sometimes have significant advantages in speed and memory efficiency, even in a high-level language like Python. + +[high-level-language]: https://en.wikipedia.org/wiki/High-level_programming_language +[binary-digits]: https://www.khanacademy.org/computing/computers-and-internet/xcae6f4a7ff015e7d:digital-information/xcae6f4a7ff015e7d:binary-numbers/v/the-binary-number-system +[bits]: https://en.wikipedia.org/wiki/Bit +[python-bitwise-operations]: https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations +[python-bitwise-operators]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations diff --git a/concepts/bitwise-operators/links.json b/concepts/bitwise-operators/links.json index eb5fb7c38a..7c103c8463 100644 --- a/concepts/bitwise-operators/links.json +++ b/concepts/bitwise-operators/links.json @@ -1,18 +1,22 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://wiki.python.org/moin/BitwiseOperators/", + "description": "BitwiseOperators on the Python wiki." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://realpython.com/python-bitwise-operators", + "description": "Real Python: Bitwise Operators in Python." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://stackoverflow.com/a/20768199", + "description": "Stack Overflow: Convert a Python int to an unsigned int." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://www.khanacademy.org/computing/computer-science/cryptography/ciphers/a/xor-bitwise-operation", + "description": "Khan Academy: The Ultimate Shift Cipher." + }, + { + "url": "https://en.wikipedia.org/wiki/XOR_cipher", + "description": "The XOR Cipher" } ] From 4dd9e792e6ca3afc5d8308b67600a7bcbb8c7f82 Mon Sep 17 00:00:00 2001 From: colinleach Date: Fri, 12 Jan 2024 20:03:40 -0700 Subject: [PATCH 577/826] [Leap]: Add `calendar-isleap` approach and improved benchmarks (#3583) * [Leap]: Add `calendar-isleap` approach and improved benchmarks * added comments on `isleap()` implementation and operator precedence * Approach and Chart Edits Various language edits and touchups. Added chart reference and alt-textm as well as link to long description. Probably unnecessary for this particular article but wanted to establish an example of what to do when alt-texting a chart. See https://www.w3.org/WAI/tutorials/images/complex/ for more information and examples on how to alt-text complex images. * Fixed Awkward Chart Intro * Another try on alt-text * Trying Yet Again * Attempt to align figcaption * Giving up and going for description location in alt --------- Co-authored-by: BethanyG --- .../leap/.approaches/boolean-chain/content.md | 25 +++- .../.approaches/calendar-isleap/content.md | 38 ++++++ .../.approaches/calendar-isleap/snippet.txt | 4 + .../practice/leap/.approaches/config.json | 10 +- .../.approaches/datetime-addition/content.md | 9 +- .../practice/leap/.approaches/introduction.md | 62 ++++++--- exercises/practice/leap/.articles/config.json | 3 +- .../.articles/performance/code/Benchmark.py | 123 +++++++++--------- .../leap/.articles/performance/content.md | 58 ++++++--- 9 files changed, 222 insertions(+), 110 deletions(-) create mode 100644 exercises/practice/leap/.approaches/calendar-isleap/content.md create mode 100644 exercises/practice/leap/.approaches/calendar-isleap/snippet.txt diff --git a/exercises/practice/leap/.approaches/boolean-chain/content.md b/exercises/practice/leap/.approaches/boolean-chain/content.md index c909863fca..d4c78911e1 100644 --- a/exercises/practice/leap/.approaches/boolean-chain/content.md +++ b/exercises/practice/leap/.approaches/boolean-chain/content.md @@ -6,13 +6,15 @@ def leap_year(year): ``` +This might be considered the "most idiomatic" or "most Pythonic" solution, as it is exactly the same as the code implemented by the maintainers of the Python language for the [`calendar.isleap()`][isleap-source] method. + The first boolean expression uses the [modulo operator][modulo-operator] to check if the year is evenly divided by `4`. -- If the year is not evenly divisible by `4`, then the chain will "short circuit" due to the next operator being a [logical AND][logical-and] {`and`), and will return `False`. +- If the year is _not_ evenly divisible by `4`, then the chain will [short circuit][short-ciruiting] due to the next operator being a [logical AND][logical-and] {`and`), and will return `False`. - If the year _is_ evenly divisible by `4`, then the year is checked to _not_ be evenly divisible by `100`. -- If the year is not evenly divisible by `100`, then the expression is `True` and the chain will "short-circuit" to return `True`, -since the next operator is a [logical OR][logical-or] (`or`). +- If the year is not evenly divisible by `100`, then the expression is `True` and the interpreter will stop the evaluation to return `True`, since the next operator is a [logical OR][logical-or] (`or`). - If the year _is_ evenly divisible by `100`, then the expression is `False`, and the returned value from the chain will be if the year is evenly divisible by `400`. + | year | year % 4 == 0 | year % 100 != 0 | year % 400 == 0 | is leap year | | ---- | ------------- | --------------- | --------------- | ------------ | | 2020 | True | True | not evaluated | True | @@ -21,13 +23,24 @@ since the next operator is a [logical OR][logical-or] (`or`). | 1900 | True | False | False | False | -The chain of boolean expressions is efficient, as it proceeds from testing the most likely to least likely conditions. +The chain of boolean expressions is efficient, as it proceeds from testing the most to least likely conditions. It is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. + +## Operator precedence + +The implementation contains one set of parentheses, around the `or` clause: +- One set is enough, because the `%` operator is highest priority, then the `==` and `!=` relational operators. +- Those parentheses are required, because `and` is higher priority than `or`. +In Python, `a and b or c` is interpreted as `(a and b) or c`, which would give the wrong answer for this exercise. + +If in doubt, it is always permissible to add extra parentheses for clarity. + + ## Refactoring By using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. -For example +For example: ```python def leap_year(year): @@ -42,3 +55,5 @@ It can be thought of as the expression _not_ having a remainder. [logical-or]: https://realpython.com/python-or-operator/ [falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ [not-operator]: https://realpython.com/python-not-operator/ +[short-ciruiting]: https://mathspp.com/blog/pydonts/boolean-short-circuiting#short-circuiting-in-plain-english +[isleap-source]: https://github.com/python/cpython/blob/main/Lib/calendar.py#L141-L143 diff --git a/exercises/practice/leap/.approaches/calendar-isleap/content.md b/exercises/practice/leap/.approaches/calendar-isleap/content.md new file mode 100644 index 0000000000..0674eb56a4 --- /dev/null +++ b/exercises/practice/leap/.approaches/calendar-isleap/content.md @@ -0,0 +1,38 @@ +# The `calendar.isleap()` function + +```pythoon +from calendar import isleap + +def leap_year(year): + return isleap(year) +``` + +~~~~exercism/caution +This approach may be considered a "cheat" for this exercise, which is intended to practice Boolean operators and logic. +~~~~ + + +The Python standard library includes a [`calendar`][calendar] module for working with many aspects of dates in the [Gregorian calendar][gregorian-calendar]. + +One of the methods provided is [`isleap()`][isleap], which implements exactly the same functionality as this exercise. + +This is not a good way to practice the use of Booleans, as the exercise intends. +However, it may be convenient (_and better tested_) if you are working with calendar functions more broadly. + +## The library function + +This is the [implementation][implementation]: + +```python +def isleap(year): + """Return True for leap years, False for non-leap years.""" + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) +``` + +We can see that `calendar.isleap()` is just syntactic sugar for the `boolean-chain` approach. + + +[calendar]: https://docs.python.org/3/library/calendar.html +[gregorian-calendar]: https://en.wikipedia.org/wiki/Gregorian_calendar +[implementation]: https://github.com/python/cpython/blob/main/Lib/calendar.py +[isleap]: https://docs.python.org/3/library/calendar.html diff --git a/exercises/practice/leap/.approaches/calendar-isleap/snippet.txt b/exercises/practice/leap/.approaches/calendar-isleap/snippet.txt new file mode 100644 index 0000000000..220c74a265 --- /dev/null +++ b/exercises/practice/leap/.approaches/calendar-isleap/snippet.txt @@ -0,0 +1,4 @@ +from calendar import isleap + +def leap_year(year): + return isleap(year) diff --git a/exercises/practice/leap/.approaches/config.json b/exercises/practice/leap/.approaches/config.json index 3b5d57f997..515f702b76 100644 --- a/exercises/practice/leap/.approaches/config.json +++ b/exercises/practice/leap/.approaches/config.json @@ -1,7 +1,7 @@ { "introduction": { "authors": ["bobahop"], - "contributors": [] + "contributors": ["colinleach"] }, "approaches": [ { @@ -24,6 +24,14 @@ "title": "datetime addition", "blurb": "Use datetime addition.", "authors": ["bobahop"] + }, + { + "uuid": "d85be356-211a-4d2f-8af0-fa92e390b0b3", + "slug": "calendar-isleap", + "title": "calendar.isleap() function", + "blurb": "Use the calendar module.", + "authors": ["colinleach", + "BethanyG"] } ] } diff --git a/exercises/practice/leap/.approaches/datetime-addition/content.md b/exercises/practice/leap/.approaches/datetime-addition/content.md index 184869d131..3c28ef480e 100644 --- a/exercises/practice/leap/.approaches/datetime-addition/content.md +++ b/exercises/practice/leap/.approaches/datetime-addition/content.md @@ -11,11 +11,14 @@ def leap_year(year): ``` ~~~~exercism/caution -This approach may be considered a "cheat" for this exercise. +This approach may be considered a "cheat" for this exercise, which is intended to practice Boolean operators and logic. +It also adds a tremendous amount of overhead in both performance and memory, as it imports all of the `datetime` module and requires the instantiation of both a `datetime` object and a `datetime.timedelta` object. + +For more information, see this exercises performance article. ~~~~ -By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. -If it is the 29th, then the function returns `True` for the year being a leap year. +By adding a day to February 28th for a given year, you can see if the new day falls on the 29th of February, or the 1st of March. +If it is February 29th, then the function returns `True` for the year being a leap year. - A new [datetime][datetime] object is created for February 28th of the year. - Then the [timedelta][timedelta] of one day is added to that `datetime`, diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md index a5c8c829a5..4b136dd6b4 100644 --- a/exercises/practice/leap/.approaches/introduction.md +++ b/exercises/practice/leap/.approaches/introduction.md @@ -1,15 +1,16 @@ # Introduction -There are various idiomatic approaches to solve Leap. -You can use a chain of boolean expressions to test the conditions. -Or you can use a [ternary operator][ternary-operator]. +There are multiple idiomatic approaches to solving the Leap exercise. +You can use a chain of boolean expressions to test the conditions, a [ternary operator][ternary-operator], or built-in methods from the `datetime` or `calendar` modules. -## General guidance -The key to solving Leap is to know if the year is evenly divisible by `4`, `100` and `400`. +## General Guidance + +The key to efficiently solving Leap is to calculate if the year is evenly divisible by `4`, `100` and `400`. For determining that, you will use the [modulo operator][modulo-operator]. -## Approach: Chain of Boolean expressions + +## Approach: Chain of Boolean Expressions ```python def leap_year(year): @@ -17,9 +18,10 @@ def leap_year(year): ``` -For more information, check the [Boolean chain approach][approach-boolean-chain]. +For more information, see the [Boolean chain approach][approach-boolean-chain]. + -## Approach: Ternary operator of Boolean expressions +## Approach: Ternary Operator of Boolean Expressions ```python def leap_year(year): @@ -27,32 +29,50 @@ def leap_year(year): ``` -For more information, check the [Ternary operator approach][approach-ternary-operator]. +For more information, see the [Ternary operator approach][approach-ternary-operator]. -## Other approaches + +## Other Approaches Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: -### Approach: datetime addition -Add a day to February 28th for the year and see if the new day is the 29th. For more information, see the [`datetime` addition approach][approach-datetime-addition]. +### Approach: `datetime` Addition + +Add a day to February 28th for the year and see if the new day is the 29th. +However, this approach may trade speed for convenience. +For more information, see the [`datetime` addition approach][approach-datetime-addition]. + + +### Approach: The `calendar` module + +It is possible to use `calendar.isleap(year)` from the standard library, which solves this exact problem. + +This is self-defeating in an Exercism practice exercise intended to explore ways to use booleans. +In a wider context, anyone testing for leap years may already be using `calendar` or related modules, and it is good to know what library functions are available. + ## Which approach to use? -- The chain of boolean expressions should be the most efficient, as it proceeds from the most likely to least likely conditions. +- The chain of boolean expressions should be the most efficient, as it proceeds from the most to least likely conditions and takes advantage of short-circuiting. It has a maximum of three checks. -It is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. +It is the fastest approach when testing a year that is not evenly divisible by `100` that is not a leap year. Since most years fit those conditions, it is overall the most efficient approach. -- The ternary operator has a maximum of only two checks, but it starts from a less likely condition. +It also happens to be the approach taken by the maintainers of the Python language in [implementing `calendar.isleap()`][calendar_isleap-code]. + + +- The ternary operator approach has a maximum of only two checks, but it starts from a less likely condition. The ternary operator was faster in benchmarking when the year was a leap year or was evenly divisible by `100`, -but those are the least likely conditions. -- Using `datetime` addition may be considered a "cheat" for the exercise, and it was slower than the other approaches in benchmarking. +but those are the _least likely_ conditions. +- Using `datetime` addition may be considered a "cheat" for the exercise, and it was slower by far than the other approaches in benchmarking. -For more information, check the [Performance article][article-performance]. +For more information, check out the [Performance article][article-performance]. -[modulo-operator]: https://realpython.com/python-modulo-operator/ -[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ [approach-boolean-chain]: https://exercism.org/tracks/python/exercises/leap/approaches/boolean-chain -[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/leap/approaches/ternary-operator [approach-datetime-addition]: https://exercism.org/tracks/python/exercises/leap/approaches/datetime-addition +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/leap/approaches/ternary-operator [article-performance]: https://exercism.org/tracks/python/exercises/leap/articles/performance +[calendar_isleap-code]: https://github.com/python/cpython/blob/3.9/Lib/calendar.py#L100-L102 +[modulo-operator]: https://realpython.com/python-modulo-operator/ +[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ + diff --git a/exercises/practice/leap/.articles/config.json b/exercises/practice/leap/.articles/config.json index acafe3b22d..009c0ab016 100644 --- a/exercises/practice/leap/.articles/config.json +++ b/exercises/practice/leap/.articles/config.json @@ -5,7 +5,8 @@ "slug": "performance", "title": "Performance deep dive", "blurb": "Deep dive to find out the most performant approach for determining a leap year.", - "authors": ["bobahop"] + "authors": ["bobahop", + "colinleach"] } ] } diff --git a/exercises/practice/leap/.articles/performance/code/Benchmark.py b/exercises/practice/leap/.articles/performance/code/Benchmark.py index 467a595292..d2a204b361 100644 --- a/exercises/practice/leap/.articles/performance/code/Benchmark.py +++ b/exercises/practice/leap/.articles/performance/code/Benchmark.py @@ -1,88 +1,91 @@ import timeit - +import pandas as pd +import numpy as np +import matplotlib as mpl +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.colors import ListedColormap + +# Setting up the Data loops = 1_000_000 -val = timeit.timeit("""leap_year(1900)""", - """ -def leap_year(year): - return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) +row_headers = ["if-statements", "ternary", "datetime-add", "calendar-isleap"] +col_headers = ["1900", "2000", "2019", "2020"] -""", number=loops) / loops +# empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) -print(f"if statements 1900: {val}") +setups = {} -val = timeit.timeit("""leap_year(2000)""", - """ +setups["if-statements"] = """ def leap_year(year): return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) +""" -""", number=loops) / loops - -print(f"if statements 2000: {val}") - - -val = timeit.timeit("""leap_year(2019)""", - """ -def leap_year(year): - return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) - -""", number=loops) / loops - -print(f"if statements 2019: {val}") - -val = timeit.timeit("""leap_year(2020)""", - """ +setups["ternary"] = """ def leap_year(year): - return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) - -""", number=loops) / loops + return not year % 400 if not year % 100 else not year % 4 +""" -print(f"if statements 2020: {val}") +setups["datetime-add"] = """ +from datetime import datetime, timedelta -val = timeit.timeit("""leap_year(1900)""", - """ def leap_year(year): - return not year % 400 if not year % 100 else not year % 4 - -""", number=loops) / loops + return (datetime(year, 2, 28) + timedelta(days=1)).day == 29 +""" -print(f"ternary 1900: {val}") +setups["calendar-isleap"] = """ +from calendar import isleap -val = timeit.timeit("""leap_year(2000)""", - """ def leap_year(year): - return not year % 400 if not year % 100 else not year % 4 + return isleap(year) +""" -""", number=loops) / loops -print(f"ternary 2000: {val}") +# Conducting ghe timings +for descriptor in row_headers: + val = timeit.timeit("""leap_year(1900)""", setups[descriptor], number=loops) / loops + year = '1900' + print(f"{descriptor} {year}: {val}") + df.loc[descriptor, year] = val -val = timeit.timeit("""leap_year(2019)""", - """ -def leap_year(year): - return not year % 400 if not year % 100 else not year % 4 + val = timeit.timeit("""leap_year(2000)""", setups[descriptor], number=loops) / loops + year = '2000' + print(f"{descriptor} {year}: {val}") + df.loc[descriptor, year] = val -""", number=loops) / loops + val = timeit.timeit("""leap_year(2019)""", setups[descriptor], number=loops) / loops + year = '2019' + print(f"{descriptor} {year}: {val}") + df.loc[descriptor, year] = val -print(f"ternary 2019: {val}") + val = timeit.timeit("""leap_year(2020)""", setups[descriptor], number=loops) / loops + year = '2020' + print(f"{descriptor} {year}: {val}") + df.loc[descriptor, year] = val -val = timeit.timeit("""leap_year(2020)""", - """ -def leap_year(year): - return not year % 400 if not year % 100 else not year % 4 -""", number=loops) / loops +# Settng up chart details and colors +mpl.rcParams['axes.labelsize'] = 18 +bar_colors = ["#AFAD6A", "#B1C9FD", "#CDC6FD", + "#FABD19", "#3B76F2", "#7467D1", + "#FA9A19", "#85832F", "#1A54CE","#4536B0"] -print(f"ternary 2020: {val}") +my_cmap = ListedColormap(sns.color_palette(bar_colors, as_cmap=True)) -val = timeit.timeit("""leap_year(2019)""", - """ -import datetime +# bar plot of actual run times +ax = df.plot.bar(figsize=(10, 7), + ylabel="time (s)", + fontsize=14, + width=0.8, + rot=0, + colormap=my_cmap) -def leap_year(year): - return (datetime.datetime(year, 2, 28) + - datetime.timedelta(days=1)).day == 29 +# Saving the graph for later use +plt.savefig('../timeit_bar_plot.svg') -""", number=loops) / loops -print(f"datetime add 2019: {val}") +# The next bit will be useful for `introduction.md` +# pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".1e")) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index ce5ead1ca2..5edf207a9f 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -1,39 +1,59 @@ # Performance -In this approach, we'll find out how to most efficiently calculate if a year is a leap year in Python. +In this article, we'll find out how to most efficiently calculate if a year is a leap year in Python. The [approaches page][approaches] lists two idiomatic approaches to this exercise: 1. [Using the boolean chain][approach-boolean-chain] 2. [Using the ternary operator][approach-ternary-operator] -For our performance investigation, we'll also include a third approach that [uses datetime addition][approach-datetime-addition]. +For our performance investigation, we will also include a two further approaches: +3. [datetime addition][approach-datetime-addition] +4. The [`calendar.isleap()`][approach-calendar-isleap] function from the calendar module in the standard library + ## Benchmarks -To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. - -``` -if statements 1900: 1.468243999988772e-07 -if statements 2000: 1.3710349999018945e-07 -if statements 2019: 8.861289999913425e-08 -if statements 2020: 1.21072500012815e-07 -ternary 1900: 1.091794999956619e-07 -ternary 2000: 1.0275900000124239e-07 -ternary 2019: 1.0278620000462979e-07 -ternary 2020: 1.0290379999787546e-07 -datetime add 2019: 6.689728000201284e-07 -``` - -- The boolean chain is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] module. +All methods are "fast", but the difference may be easier to see graphically. +**Note**: The y-axis values in the chart have a `1e-7` multiplier. + All run times are sub-microsecond. + + +

+ Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. +

+ + +### Timings for approaches by input year + + +| | 1900 | 2000 | 2019 | 2020 | +|:----------------|--------:|--------:|--------:|--------:| +| if statements | 5.1e-08 | 4.5e-08 | 2.5e-08 | 3.9e-08 | +| ternary | 3.3e-08 | 3.0e-08 | 3.0e-08 | 3.3e-08 | +| datetime add | 2.8e-07 | 2.8e-07 | 2.7e-07 | 2.8e-07 | +| calendar isleap | 6.5e-08 | 5.8e-08 | 3.8e-08 | 5.5e-08 | + + +- The `if-statements` (_boolean chain_) is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. Since most years fit those conditions, it is overall the most efficient approach. - The ternary operator is faster in benchmarking when the year is a leap year or is evenly divisible by `100`, but those are the least likely conditions. - Adding to the `datetime` may not only be a "cheat", but it is slower than the other approaches. + - Comparing `import datatime` and `from datetime import datetime, timedelta` showed little speed difference _(data not shown)_. +- Using the built-in `calendar.isleap()` function is terse, convenient and very readable, but not quite as fast as writing your own logic. +This is likely due to the overhead of both loading the `calendar` module and then calling `calendar.isleap()`. + +Often, it is helpful to the programmer to use imported packages, but a large `import` to use a simple function may not give the fastest code. +Consider the context, and decide which is best for you in each case. -[approaches]: https://exercism.org/tracks/python/exercises/leap/approaches [approach-boolean-chain]: https://exercism.org/tracks/python/exercises/leap/approaches/boolean-chain -[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/leap/approaches/ternary-operator +[approach-calendar-isleap]: https://exercism.org/tracks/python/exercises/leap/approaches/calendar-isleap [approach-datetime-addition]: https://exercism.org/tracks/python/exercises/leap/approaches/datetime-addition +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/leap/approaches/ternary-operator +[approaches]: https://exercism.org/tracks/python/exercises/leap/approaches [benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/leap/.articles/performance/code/Benchmark.py [timeit]: https://docs.python.org/3/library/timeit.html From e602bcb5d4259f509ad1d8c293c455696409df0a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 12 Jan 2024 19:15:31 -0800 Subject: [PATCH 578/826] Update content.md remove

tag from around svg image (#3587) * Update content.md remove

tag from around svg image Noticed after the push that the image was showing as broken on dark theme. Hoping this helps. * Update content.md put whole tag on one line, in case that makes a difference. --- .../practice/leap/.articles/performance/content.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index 5edf207a9f..9901a1e0ff 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -19,16 +19,11 @@ All methods are "fast", but the difference may be easier to see graphically. **Note**: The y-axis values in the chart have a `1e-7` multiplier. All run times are sub-microsecond. - -

- Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. -

- + Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. ### Timings for approaches by input year +
| | 1900 | 2000 | 2019 | 2020 | |:----------------|--------:|--------:|--------:|--------:| @@ -37,6 +32,7 @@ All methods are "fast", but the difference may be easier to see graphically. | datetime add | 2.8e-07 | 2.8e-07 | 2.7e-07 | 2.8e-07 | | calendar isleap | 6.5e-08 | 5.8e-08 | 3.8e-08 | 5.5e-08 | +
- The `if-statements` (_boolean chain_) is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. Since most years fit those conditions, it is overall the most efficient approach. From 5eed8cf9bb162bb61e01734fc19f347be785c2e5 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 12 Jan 2024 19:29:23 -0800 Subject: [PATCH 579/826] Update content.md with the correct link this time! (#3588) --- exercises/practice/leap/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index 9901a1e0ff..7e8d41dae5 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -19,7 +19,7 @@ All methods are "fast", but the difference may be easier to see graphically. **Note**: The y-axis values in the chart have a `1e-7` multiplier. All run times are sub-microsecond. - Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. + Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. ### Timings for approaches by input year From 3705a4964b41e3b9821aed9503954e424aabbefc Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 13 Jan 2024 12:45:11 -0800 Subject: [PATCH 580/826] Swapped HTML syntax for markdown for svg image. (#3590) --- exercises/practice/leap/.articles/performance/content.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index 7e8d41dae5..c23e419a14 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -19,7 +19,8 @@ All methods are "fast", but the difference may be easier to see graphically. **Note**: The y-axis values in the chart have a `1e-7` multiplier. All run times are sub-microsecond. - Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. +!["Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year."](https://assets.exercism.org/images/tracks/python/leap/leap_timeit_bar_plot-light.svg) + ### Timings for approaches by input year From 63ab89fd1931468209bdc722c59d251556d1fb3f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 14 Jan 2024 15:20:08 -0800 Subject: [PATCH 581/826] Synced leap to problem specifications. (#3592) --- exercises/practice/leap/.docs/instructions.md | 21 +------------------ exercises/practice/leap/.docs/introduction.md | 16 ++++++++++++++ exercises/practice/leap/.meta/config.json | 2 +- 3 files changed, 18 insertions(+), 21 deletions(-) create mode 100644 exercises/practice/leap/.docs/introduction.md diff --git a/exercises/practice/leap/.docs/instructions.md b/exercises/practice/leap/.docs/instructions.md index a83826b2e0..b14f8565d6 100644 --- a/exercises/practice/leap/.docs/instructions.md +++ b/exercises/practice/leap/.docs/instructions.md @@ -1,22 +1,3 @@ # Instructions -Given a year, report if it is a leap year. - -The tricky thing here is that a leap year in the Gregorian calendar occurs: - -```text -on every year that is evenly divisible by 4 - except every year that is evenly divisible by 100 - unless the year is also evenly divisible by 400 -``` - -For example, 1997 is not a leap year, but 1996 is. -1900 is not a leap year, but 2000 is. - -## Notes - -Though our exercise adopts some very simple rules, there is more to learn! - -For a delightful, four minute explanation of the whole leap year phenomenon, go watch [this youtube video][video]. - -[video]: https://www.youtube.com/watch?v=xX96xng7sAE +Your task is to determine whether a given year is a leap year. diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md new file mode 100644 index 0000000000..1cb8b14ccb --- /dev/null +++ b/exercises/practice/leap/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +A leap year (in the Gregorian calendar) occurs: + +- In every year that is evenly divisible by 4 +- Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. + +Some examples: + +- 1997 was not a leap year as it's not divisible by 4. +- 1900 was not a leap year as it's not divisible by 400 +- 2000 was a leap year! + +~~~~exercism/note +For a delightful, four minute explanation of the whole phenomenon of leap years, check out [this youtube video](https://www.youtube.com/watch?v=xX96xng7sAE). +~~~~ diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index fd2bd24087..1c35f22be1 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -32,7 +32,7 @@ ".meta/example.py" ] }, - "blurb": "Given a year, report if it is a leap year.", + "blurb": "Determine whether a given year is a leap year.", "source": "CodeRanch Cattle Drive, Assignment 3", "source_url": "https://coderanch.com/t/718816/Leap" } From 03229bcca3ca7577364593ae68c415087d720e85 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 14 Jan 2024 17:13:29 -0800 Subject: [PATCH 582/826] [Leap]: Updated Table in Performance Article with Newer Timings to Match Graph. (#3593) * Updated table with newer timings to match graph. * Apply suggestions from code review Further adjustment to the numbers. _sigh_ --- exercises/practice/leap/.articles/performance/content.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index c23e419a14..67d7b57b63 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -28,10 +28,10 @@ All methods are "fast", but the difference may be easier to see graphically. | | 1900 | 2000 | 2019 | 2020 | |:----------------|--------:|--------:|--------:|--------:| -| if statements | 5.1e-08 | 4.5e-08 | 2.5e-08 | 3.9e-08 | -| ternary | 3.3e-08 | 3.0e-08 | 3.0e-08 | 3.3e-08 | -| datetime add | 2.8e-07 | 2.8e-07 | 2.7e-07 | 2.8e-07 | -| calendar isleap | 6.5e-08 | 5.8e-08 | 3.8e-08 | 5.5e-08 | +| if-statements | 1.7e-07 | 1.6e-07 | 9.0e-08 | 1.3e-07 | +| ternary | 1.2e-07 | 1.0e-07 | 1.1e-07 | 1.1e-07 | +| datetime-add | 6.9e-07 | 6.7e-07 | 7.0e-07 | 6.7e-07 | +| calendar-isleap | 2.2e-07 | 2.2e-07 | 1.4e-07 | 1.7e-07 |
From f4a14bc5db5d7834cebac42569bd4ca644e6f68d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 16 Jan 2024 19:31:51 +0100 Subject: [PATCH 583/826] Sync the `leap` exercise's docs with the latest data. (#3595) --- exercises/practice/leap/.docs/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md index 1cb8b14ccb..4ffd2da594 100644 --- a/exercises/practice/leap/.docs/introduction.md +++ b/exercises/practice/leap/.docs/introduction.md @@ -2,15 +2,15 @@ A leap year (in the Gregorian calendar) occurs: -- In every year that is evenly divisible by 4 +- In every year that is evenly divisible by 4. - Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. Some examples: - 1997 was not a leap year as it's not divisible by 4. -- 1900 was not a leap year as it's not divisible by 400 +- 1900 was not a leap year as it's not divisible by 400. - 2000 was a leap year! ~~~~exercism/note -For a delightful, four minute explanation of the whole phenomenon of leap years, check out [this youtube video](https://www.youtube.com/watch?v=xX96xng7sAE). +For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE). ~~~~ From 283716ec30129667151f010b45e01a8e9a4a2bac Mon Sep 17 00:00:00 2001 From: Knute Snortum Date: Tue, 16 Jan 2024 12:09:04 -0800 Subject: [PATCH 584/826] Fixes #3594, solutions are natural numbers (#3597) --- exercises/practice/square-root/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/square-root/.docs/instructions.append.md b/exercises/practice/square-root/.docs/instructions.append.md index 535038e466..84b4cf8ee7 100644 --- a/exercises/practice/square-root/.docs/instructions.append.md +++ b/exercises/practice/square-root/.docs/instructions.append.md @@ -6,7 +6,7 @@ Python offers a wealth of mathematical functions in the form of the [math module][math-module] and built-ins such as [`pow()`][pow] and [`sum()`][sum]. However, we'd like you to consider the challenge of solving this exercise without those built-ins or modules. -While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of only testing [natural numbers][nautral-number] (positive integers). +While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of having only [natural numbers][nautral-number] (positive integers) as solutions. [math-module]: https://docs.python.org/3/library/math.html From bd8209708ad360323f5f1e0d2ea2309677cc29ae Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Wed, 17 Jan 2024 01:40:15 +0530 Subject: [PATCH 585/826] fix typo (#3584) --- .../.approaches/nested-for-loop-optimized/content.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md index 2bc2718391..da24f2c234 100644 --- a/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md +++ b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md @@ -72,6 +72,7 @@ y = [99, 98] ... x = [90, 89, 88 ...] y = [99, 98,97,96,95,94,93,92,91,90] +``` Here we can see that the highest value for this "run" is 90 \* 99 = 8910. Meaning that running beyond this point won't give us any values higher than 9009. From 2a276fb5e3ca4251b8a88cd47ff0edfe50fc6699 Mon Sep 17 00:00:00 2001 From: Luis F M Alves <45541532+lfmalves@users.noreply.github.com> Date: Wed, 17 Jan 2024 13:59:49 -0300 Subject: [PATCH 586/826] Update introduction.md (typo) (#3596) Small typo on the intro to the exercise "meltdown mitigation" --- exercises/concept/meltdown-mitigation/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/meltdown-mitigation/.docs/introduction.md b/exercises/concept/meltdown-mitigation/.docs/introduction.md index fe9fff4d5d..0eb3d88ccd 100644 --- a/exercises/concept/meltdown-mitigation/.docs/introduction.md +++ b/exercises/concept/meltdown-mitigation/.docs/introduction.md @@ -48,7 +48,7 @@ if x > y: elif y > z: print("y is greater than x and z") else: - print("z is great than x and y") + print("z is greater than x and y") ... >>> z is great than x and y ``` From e1879a3f60ef34933d75498fb7f45243b6670017 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 18 Jan 2024 20:13:55 +0100 Subject: [PATCH 587/826] reverse-string: sync (#3598) * Sync the `reverse-string` exercise's docs with the latest data. * Sync the `reverse-string` exercise's metadata with the latest data. --- .../practice/reverse-string/.docs/instructions.md | 10 ++++++---- .../practice/reverse-string/.docs/introduction.md | 5 +++++ exercises/practice/reverse-string/.meta/config.json | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/reverse-string/.docs/introduction.md diff --git a/exercises/practice/reverse-string/.docs/instructions.md b/exercises/practice/reverse-string/.docs/instructions.md index 039ee33ae5..0ff4198e46 100644 --- a/exercises/practice/reverse-string/.docs/instructions.md +++ b/exercises/practice/reverse-string/.docs/instructions.md @@ -1,7 +1,9 @@ # Instructions -Reverse a string +Your task is to reverse a given string. -For example: -input: "cool" -output: "looc" +Some examples: + +- Turn `"stressed"` into `"desserts"`. +- Turn `"strops"` into `"sports"`. +- Turn `"racecar"` into `"racecar"`. diff --git a/exercises/practice/reverse-string/.docs/introduction.md b/exercises/practice/reverse-string/.docs/introduction.md new file mode 100644 index 0000000000..02233e0755 --- /dev/null +++ b/exercises/practice/reverse-string/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +Reversing strings (reading them from right to left, rather than from left to right) is a surprisingly common task in programming. + +For example, in bioinformatics, reversing the sequence of DNA or RNA strings is often important for various analyses, such as finding complementary strands or identifying palindromic sequences that have biological significance. diff --git a/exercises/practice/reverse-string/.meta/config.json b/exercises/practice/reverse-string/.meta/config.json index 1df068a958..bbc16e8661 100644 --- a/exercises/practice/reverse-string/.meta/config.json +++ b/exercises/practice/reverse-string/.meta/config.json @@ -22,7 +22,7 @@ ".meta/example.py" ] }, - "blurb": "Reverse a string.", + "blurb": "Reverse a given string.", "source": "Introductory challenge to reverse an input string", "source_url": "https://medium.freecodecamp.org/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb" } From 6f1fa934160cd501d8c8aa34a44ea37366de8679 Mon Sep 17 00:00:00 2001 From: colinleach Date: Fri, 19 Jan 2024 19:34:30 -0700 Subject: [PATCH 588/826] [Pythagorean Triplet]: Approaches Draft (#3582) * [Pythagorean Triplet]: Approaches Draft * [Leap]: Add `isleap` approach and improved benchmarks * Delete exercises/practice/leap/.articles/performance/timeit_bar_plot.svg Sorry, I committed this to the wrong branch. Embarrassingly amateurish! * Suggestions, Edits, etc. Suggestions and edits. Because we need to PR to a different repo, the `.svg` images have been removed, and the text adjusted. --------- Co-authored-by: BethanyG --- .../.approaches/config.json | 33 +++ .../.approaches/cubic/content.md | 24 +++ .../.approaches/cubic/snippet.txt | 8 + .../.approaches/introduction.md | 192 ++++++++++++++++++ .../.approaches/linear/content.md | 59 ++++++ .../.approaches/linear/snippet.txt | 8 + .../.approaches/quadratic/content.md | 47 +++++ .../.approaches/quadratic/snippet.txt | 8 + .../pythagorean-triplet/.articles/config.json | 12 ++ .../.articles/performance/code/Benchmark.py | 124 +++++++++++ .../performance/code/create_plots.py | 42 ++++ .../performance/code/fit_gradients.py | 38 ++++ .../performance/code/run_times.feather | Bin 0 -> 5426 bytes .../performance/code/transposed_logs.feather | Bin 0 -> 4010 bytes .../.articles/performance/content.md | 102 ++++++++++ .../.articles/performance/snippet.md | 8 + 16 files changed, 705 insertions(+) create mode 100644 exercises/practice/pythagorean-triplet/.approaches/config.json create mode 100644 exercises/practice/pythagorean-triplet/.approaches/cubic/content.md create mode 100644 exercises/practice/pythagorean-triplet/.approaches/cubic/snippet.txt create mode 100644 exercises/practice/pythagorean-triplet/.approaches/introduction.md create mode 100644 exercises/practice/pythagorean-triplet/.approaches/linear/content.md create mode 100644 exercises/practice/pythagorean-triplet/.approaches/linear/snippet.txt create mode 100644 exercises/practice/pythagorean-triplet/.approaches/quadratic/content.md create mode 100644 exercises/practice/pythagorean-triplet/.approaches/quadratic/snippet.txt create mode 100644 exercises/practice/pythagorean-triplet/.articles/config.json create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/create_plots.py create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/fit_gradients.py create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/run_times.feather create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/transposed_logs.feather create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/content.md create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/snippet.md diff --git a/exercises/practice/pythagorean-triplet/.approaches/config.json b/exercises/practice/pythagorean-triplet/.approaches/config.json new file mode 100644 index 0000000000..b9514f490a --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/config.json @@ -0,0 +1,33 @@ +{ + "introduction": { + "authors": ["colinleach", + "BethanyG"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "4d4b4e6c-a026-4ed3-8e07-6b9edfabe713", + "slug": "cubic", + "title": "Cubic", + "blurb": "Cubic-time approach with loops nested 3 deep.", + "authors": ["colinleach", + "BethanyG"] + }, + { + "uuid": "385c0ace-117f-4480-8dfc-0632d8893e60", + "slug": "quadratic", + "title": "Quadratic", + "blurb": "Quadratic-time approaches with doubly-nested loops.", + "authors": ["colinleach", + "BethanyG"] + }, + { + "uuid": "1addb672-6064-4a07-acad-4a08f92d9e43", + "slug": "linear", + "title": "Linear Loop", + "blurb": "Linear-time approaches with no nesting of loops.", + "authors": ["colinleach", + "BethanyG"] + } + ] +} diff --git a/exercises/practice/pythagorean-triplet/.approaches/cubic/content.md b/exercises/practice/pythagorean-triplet/.approaches/cubic/content.md new file mode 100644 index 0000000000..9611745c2a --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/cubic/content.md @@ -0,0 +1,24 @@ +# Cubic-time triply-nested loops + +```python +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + for c in range(b + 1, n + 1): + if a**2 + b**2 == c**2 and a + b + c == n: + triplets.append([a, b, c]) + return triplets +``` + +The strategy in this case is to scan through all three variables and test them in the innermost loop. +But the innermost loop will test all variables _every time_ the enclosing loop iterates. +And the enclosing loop up will iterate through all of its range _every time_ the outermost loop iterates. + +So the 'work' this code has to do for each additional value in `range(1, n+1)` is `n**3`. + +This gives code that is simple, clear, ...and so slow as to be useless for all but the smallest values of `n`. + +We could tighten up the bounds on loop variables: for example, `a` is the smallest integer of a triplet that sums to `n`, so inevitably `a < n //3`. + +However, this is not nearly enough to rescue an inappropriate algorithm. diff --git a/exercises/practice/pythagorean-triplet/.approaches/cubic/snippet.txt b/exercises/practice/pythagorean-triplet/.approaches/cubic/snippet.txt new file mode 100644 index 0000000000..eb2b55c687 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/cubic/snippet.txt @@ -0,0 +1,8 @@ +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + for c in range(b + 1, n + 1): + if a**2 + b**2 == c**2 and a + b + c == n: + triplets.append([a, b, c]) + return triplets \ No newline at end of file diff --git a/exercises/practice/pythagorean-triplet/.approaches/introduction.md b/exercises/practice/pythagorean-triplet/.approaches/introduction.md new file mode 100644 index 0000000000..ab79ef1afd --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/introduction.md @@ -0,0 +1,192 @@ +# Introduction + +The main challenge in solving the Pythagorean Triplet exercise is to come up with a 'fast enough' algorithm. +The problem space can easily become very large, and 'naive' or more 'brute force' solutions may time out on the test runner. + +There are three reasonably common variations to this problem +1. A [cubic time][approaches-cubic] solution, which uses highly nested loops and is non-performant. +2. A [quadratic time][approaches-quadratic] solution, which uses one nested loop, and is reasonably easy to figure out. +3. A [linear time][approaches-linear] solution, requiring some deeper understanding of the mathematics of finding trplets. + + +If those terms are unclear to you, you might like to read about [time complexity][time-complexity], and how it is described by [asymptotic notation][asymptotic-notation]. + +The basic idea is to study how algorithms scale (_in CPU/GPU run time, memory usage, or other resource_) as the input parameters grow toward infinity. +In this document we will focus on run time, which is critical for this exercise. + + +## General guidance + +The goal of `Pythagorean Triplets` is to find combinations of three numbers `[a, b, c]` satisfying a set of conditions: + +1. `a < b < c` +2. `a**2 + b**2 == c**2` (_otherwise known as the [Pythagorean theorem][Pythagorean-theorem]_) +3. `a + b + c == n`, where `n` is supplied as a parameter to the function. + +For a given `n`, the solution should include all valid triplets as a list of lists. + + +## Approach: Cubic time solution with 3-deep nested loops + +```python +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + for c in range(b + 1, n + 1): + if a**2 + b**2 == c**2 and a + b + c == n: + triplets.append([a, b, c]) + return triplets +``` + +This is the most 'naive' or 'brute force' approach, scanning through all possible integers `<= n` that satisfy `a < b < c`. +This might be the first thing you think of when sketching out the algorithm on paper following the exercise instructions. +It is useful to see the steps of the solution and to look at the size of the problem space. + +***Don't implement this in code!*** + +While it is a valid solution and it indeed works for small values of `n`, it becomes impossibly slow as `n` grows larger. +For any truly large values of `n`, this code might take over all the available processing power on your local computer and never complete. +For Exercism's online environment, the test suite will time out and fail. + + +## Approach: Quadratic time solution with 2-deep nested loops + +```python +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + c = n - a - b + if a ** 2 + b ** 2 == c ** 2: + triplets.append([a, b, c]) + return triplets +``` + +Given the constraint that `a + b + c == n`, we can eliminate the innermost loop from the cubic approach and calculate `c` directly. +This gives a substantial speed advantage, allowing the tests to run to completion in a reasonable time, _locally_. + +However, the Exercism online test runner will still time out with this solution. + +Examining the code, it is clear that the upper bounds on the `loop` variables are far too generous, and too much work is bing done. + + +The solution below tightens the bounds and pre-calculates `c * c` in the outer `loop`. +This gives about a 4-fold speedup, but still times out on the online test runner: + + +```python +def triplets_with_sum(n): + result = [] + for c in range(5, n - 1): + c_sq = c * c + for a in range(3, (n - c + 1) // 2): + b = n - a - c + if a < b < c and a * a + b * b == c_sq: + result.append([a, b, c]) + return result +``` + +If a quadratic-time algorithm was the best available option, there are other ways to squeeze out small performance gains. + +For bigger problems outside Exercism, there are third-party packages such as [`numpy`][numpy] or [`numba`][numba] which replace Python +loops (_versatile but relatively slow_) with routines written in C/C++, perhaps with use of the GPU. +The runtime is still proportional to `n**2`, but the proportionality constant (_which would be measured in C/C++ as opposed to Python_) may be much smaller. + +Fortunately for the present discussion, mathematicians have been studying Pythagorean Triplets for centuries: see [Wikipedia][wiki-pythag], [Wolfram MathWorld][wolfram-pythag], or many other sources. + +So mathematically there are much faster algorithms, at the expense of reduced readability. + + +## Linear time solutions + +```python +from math import sqrt + +def triplets_with_sum(n): + N = float(n) + triplets = [] + for c in range(int(N / 2) - 1, int((sqrt(2) - 1) * N), -1): + D = sqrt(c ** 2 - N ** 2 + 2 * N * c) + if D == int(D): + triplets.append([int((N - c - D) / 2), int((N - c + D) / 2), c]) + return triplets +``` + +_All clear?_ 😉 + +After some thoughtful mathematical analysis, there is now only a single loop! + +Run time is now much faster, especially for large `n`, but a reasonable person could find it quite difficult to understand what the code is doing. + +If you do things like this out in the 'real world' ***please*** document your code carefully. +It might also be helpful to choose variable names that are more descriptive to help readers understand all of the values and operations. +In a few weeks, the bare code will puzzle your future self. +People reading it for the first time are likely to struggle even more than you will. + +The code above uses fairly 'generic' programming syntax. +Another submission used the same basic algorithm but in a more 'Pythonic' way: + + +```python +def triplets_with_sum(n): + def calculate_medium(small): + return (n ** 2 - 2 * n * small) / (2 * (n - small)) + + two_sides = ((int(medium), small) for small in range(3, n // 3) + if small < (medium := calculate_medium(small)) + and medium.is_integer()) + + return [[small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides] +``` + +Although it is important to note that this solution could have chosen a better name for the `n` parameter, and clearer formatting for the `generator-expression` and the `list-comprehension`: + +```python +def triplets_with_sum(number): + def calculate_medium(small): + + # We have two numbers, but need the third. + return (number ** 2 - 2 * number * small) / (2 * (number - small)) + + two_sides = ( + (int(medium), small) for + small in range(3, number // 3) if + + #Calls calculate_medium and assigns return value to variable medium + small < (medium := calculate_medium(small)) and + medium.is_integer() + ) + + return [ + [small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides + ] +``` + + +## Which approach to use? + +If we could be sure that the code only had to handle small values of `n`, a quadratic method would have the advantage of clarity. + +However, the test suite goes up to 30_000, and the online test runner quickly times out. +We therefor need to accept some less readable code and use a linear-time implementation. + +Full details of run-time benchmarking are given in the [Performance article][article-performance]. + +Overall, the results confirm the expectation that the linear-time methods are _very much_ faster. +More surprisingly, the first example of the linear implementation (_with an explicit loop_) proved slightly faster than the more 'Pythonic' code. +This is likely due to the overhead of creating and tracking the iterator for the `generator-expression`, calculating the 'expensive' `calculate_medium()` function call within that generator, and the additional 'expensive' conversions to `int()`. + +[Pythagorean-theorem]: https://en.wikipedia.org/wiki/Pythagorean_theorem +[approaches-cubic]: https://exercism.org/tracks/python/exercises/pythagorean-triplet/approaches/cubic +[approaches-linear]: https://exercism.org/tracks/python/exercises/pythagorean-triplet/approaches/linear +[approaches-quadratic]: https://exercism.org/tracks/python/exercises/pythagorean-triplet/approaches/quadratic +[article-performance]:https://exercism.org/tracks/python/exercises/pythagorean-triplet/articles/performance +[asymptotic-notation]: https://www.khanacademy.org/computing/computer-science/algorithms/asymptotic-notation/a/asymptotic-notation +[numba]: https://numba.pydata.org/ +[numpy]: https://numpy.org/ +[time-complexity]: https://yourbasic.org/algorithms/time-complexity-explained/ +[wiki-pythag]: https://en.wikipedia.org/wiki/Pythagorean_triple +[wolfram-pythag]: https://mathworld.wolfram.com/PythagoreanTriple.html diff --git a/exercises/practice/pythagorean-triplet/.approaches/linear/content.md b/exercises/practice/pythagorean-triplet/.approaches/linear/content.md new file mode 100644 index 0000000000..93dfa8448c --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/linear/content.md @@ -0,0 +1,59 @@ +# Linear-time algorithms with no nested loops + + +The key point with this approach is that we only loop over the variable `c` in a specific range. +Some mathematical analysis (_essentially, [solving simultaneous equations][simultaneous-equasions]_) then allows us to find valid values of `a` and `b`. + +Other than that, the code syntax below is fairly mainstream across programming languages. +A related approach instead loops over `a`. + +```python +from math import sqrt + +def triplets_with_sum(n): + N = float(n) + triplets = [] + for c in range(int(N / 2) - 1, int((sqrt(2) - 1) * N), -1): + D = sqrt(c ** 2 - N ** 2 + 2 * N * c) + if D == int(D): + triplets.append([int((N - c - D) / 2), int((N - c + D) / 2), c]) + return triplets +``` + + +This second code example has no explicit `for` loop (_in Python syntax_), but the `generator-expression` and the `list-comprehension` both translate to `FOR_ITER` at the bytecode level. + So this solution is essentially the same as the first, written in a more 'Pythonic' syntax -- but that syntax does incur a small overhead in performance. + The performance hit is likely due to the extra instructions in the bytecode used to manage the `generator-expression` (_pausing the loop, resuming the loop, yielding results_) and then calling or unpacking the generator in the `list comprehension`. + However, you would have to carefully profile the code to really determine the slowdown. + + With all that said, using a `generator` or `generator-expression` with or without a `list-comprehension` might be a better strategy if your code needs to process a very large number of triplets, as it avoids storing all the results in memory until they need to be returned. + Using a `generator` or `generator-expression` by itself can also nicely set up a scenario where results are "streamed" or emitted 'on demand' for another part of the program or application. + + For more details on what these two solutions look like at the byte code level, take a look at Pythons [`dis`][dis] module. + + +```python +def triplets_with_sum(n): + def calculate_medium(small): + return (n ** 2 - 2 * n * small) / (2 * (n - small)) + + two_sides = ((int(medium), small) for small in range(3, n // 3) + if small < (medium := calculate_medium(small)) + and medium.is_integer()) + + return [[small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides] +``` + + +Some implementation details to notice: +- Nested functions, with the inner function able to reference variables such as `n` passed into the outer function. +- The generator expression creates `two_sides` as a lazily-evaluated iterator (_smaller memory footprint_) +- The [`walrus operator`][walrus-operator] `:=` is new in Python 3.8. +- The `is_integer()` method replaces `if D == int(D)`. +- Using `** 0.5` to calculate the square roots avoids a `math` import. + + +[dis]: https://docs.python.org/3/library/dis.html +[simultaneous-equasions]: https://thirdspacelearning.com/gcse-maths/algebra/simultaneous-equations/ +[walrus-operator]: https://mathspp.com/blog/pydonts/assignment-expressions-and-the-walrus-operator diff --git a/exercises/practice/pythagorean-triplet/.approaches/linear/snippet.txt b/exercises/practice/pythagorean-triplet/.approaches/linear/snippet.txt new file mode 100644 index 0000000000..a8f26825c3 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/linear/snippet.txt @@ -0,0 +1,8 @@ +def triplets_with_sum(n): + def calculate_medium(small): + return (n ** 2 - 2 * n * small) / (2 * (n - small)) + two_sides = ((int(medium), small) for small in range(3, n // 3) + if small < (medium := calculate_medium(small)) + and medium.is_integer()) + return [[small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides] diff --git a/exercises/practice/pythagorean-triplet/.approaches/quadratic/content.md b/exercises/practice/pythagorean-triplet/.approaches/quadratic/content.md new file mode 100644 index 0000000000..15f2055f9a --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/quadratic/content.md @@ -0,0 +1,47 @@ +# Quadratic-time doubly-nested loops + +```python +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + c = n - a - b + if a ** 2 + b ** 2 == c ** 2: + triplets.append([a, b, c]) + return triplets +``` + +Because `a + b + c == n`, we only loop over `a` and `b`. +The third variable `c` is then predictable. + +The above code loops over the full range of both variables. +This means the 'work' this code has to do for each additional value in `range(1, n+1)` is `n**2`. +We know enough about the problems to tighten this up a bit. + +For example: +- The smallest pythagorean is (famously) `[3, 4, 5]`, so `a >= 3` +- `a + b == n - c` and `a <= b`, so `a <= (n - c) // 2` + +We can also avoid, to some extent, repeating the same multiplication many times. +This gets us to the code below: + + +```python +def triplets_with_sum(n): + result = [] + for c in range(5, n - 1): + c_sq = c * c + for a in range(3, (n - c + 1) // 2): + b = n - a - c + if a < b < c and a * a + b * b == c_sq: + result.append([a, b, c]) + return result +``` + +We could have done a bit better. +Though not stated in the problem description, `a + b > c`, otherwise they could not form a triangle. + +This means that `c <= n // 2`, reducing the iterations in the outer loop. + +However, these optimizations only reduce the run time by a small factor. +They do almost nothing to make the algorithm scale to large `n`. diff --git a/exercises/practice/pythagorean-triplet/.approaches/quadratic/snippet.txt b/exercises/practice/pythagorean-triplet/.approaches/quadratic/snippet.txt new file mode 100644 index 0000000000..fcac4bee78 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/quadratic/snippet.txt @@ -0,0 +1,8 @@ +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + c = n - a - b + if a ** 2 + b ** 2 == c ** 2: + triplets.append([a, b, c]) + return triplets \ No newline at end of file diff --git a/exercises/practice/pythagorean-triplet/.articles/config.json b/exercises/practice/pythagorean-triplet/.articles/config.json new file mode 100644 index 0000000000..07a0789875 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.articles/config.json @@ -0,0 +1,12 @@ +{ + "articles": [ + { + "uuid": "b6ae73d5-6ee9-472d-bb48-d8eac8a097cf", + "slug": "performance", + "title": "Performance", + "blurb": "Results and analysis of timing tests for the various approaches.", + "authors": ["colinleach", + "BethanyG"] + } + ] +} diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/Benchmark.py b/exercises/practice/pythagorean-triplet/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..a00e3fc68d --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.articles/performance/code/Benchmark.py @@ -0,0 +1,124 @@ +import timeit +import pandas as pd +import numpy as np + + +n_values = (12, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000) +col_headers = [str(n) for n in n_values] +row_headers = ["cubic", "quad_loose", "quad_tight", "linear_loop", "linear_comp"] + +# empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# create a dictionary with all the solution codes + +code = { + 'cubic': """ +def triplets_with_sum(number): + triplets = [] + for a in range(1, number + 1): + for b in range(a + 1, number + 1): + for c in range(b + 1, number + 1): + if a**2 + b**2 == c**2 and a + b + c == number: + triplets.append([a, b, c]) + return triplets +""", + + 'quad_loose': """ +def triplets_with_sum(number): + triplets = [] + for a in range(1, number + 1): + for b in range(a + 1, number + 1): + c = number - a - b + if a ** 2 + b ** 2 == c ** 2: + triplets.append([a, b, c]) + return triplets +""", + + 'quad_tight': """ +def triplets_with_sum(number): + result = [] + for c in range(5, number - 1): + c_sq = c * c + for a in range(3, (number - c + 1) // 2): + b = number - a - c + if a < b < c and a * a + b * b == c_sq: + result.append([a, b, c]) + return result +""", + + 'linear_loop': """ +from math import sqrt + +def triplets_with_sum(number): + N = float(number) + triplets = [] + for c in range(int(N / 2) - 1, int((sqrt(2) - 1) * N), -1): + D = sqrt(c ** 2 - N ** 2 + 2 * N * c) + if D == int(D): + triplets.append([int((N - c - D) / 2), int((N - c + D) / 2), c]) + return triplets +""", + + 'linear_comp': """ +def triplets_with_sum(number): + def calculate_medium(small): + return (number ** 2 - 2 * number * small) / (2 * (number - small)) + + two_sides = ((int(medium), small) for small in range(3, number // 3) + if small < (medium := calculate_medium(small)) + and medium.is_integer()) + + return [[small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides] +""" +} + +# Workaround for needing to do fewer runs with slow code + +run_params = { + 'cubic': (5, n_values[:5]), + 'quad_loose': (0, n_values[:-1]), + 'quad_tight': (0, n_values[:-1]), + 'linear_loop': (1000, n_values), + 'linear_comp': (1000, n_values) +} + +# Run the timing tests - SLOW! + +for descriptor in row_headers: + loops = run_params[descriptor][0] + for n in run_params[descriptor][1]: + # ugly hack for the quadratic runs + if descriptor.startswith('quad'): + loops = 10 if n <= 10_000 else 3 + + # including a string comprehension in the timed part of the run would + # normally be a bad idea. + # For the slow runs, the overhead is insignificant in this exercise. + function_call = f"triplets_with_sum({n})" + val = timeit.timeit(function_call, code[descriptor], number=loops) / loops + + print(f"{descriptor}, n = {n:6n}: {val:.2e}") + df.loc[descriptor, str(n)] = val + +# Save the data to avoid constantly regenerating it + +df.to_feather('run_times.feather') +print("\nDataframe saved to './run_times.feather'") + +# The next bit will be useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".1e")) + + +# To plot and fit the slopes, the df needs to be log10-transformed and transposed + +pd.options.display.float_format = '{:,.2g}'.format +log_n_values = np.log10(n_values) +df[df == 0.0] = np.nan +transposed = np.log10(df).T +transposed = transposed.set_axis(log_n_values, axis=0) +transposed.to_feather('transposed_logs.feather') +print("\nDataframe saved to './transposed_logs.feather'") diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/create_plots.py b/exercises/practice/pythagorean-triplet/.articles/performance/code/create_plots.py new file mode 100644 index 0000000000..59fa8896e1 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.articles/performance/code/create_plots.py @@ -0,0 +1,42 @@ +import matplotlib as mpl +import matplotlib.pyplot as plt +import pandas as pd + + +# These dataframes are slow to create, so they should be saved in Feather format + +try: + df = pd.read_feather('./run_times.feather') +except FileNotFoundError: + print("File './run_times.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +try: + transposed = pd.read_feather('./transposed_logs.feather') +except FileNotFoundError: + print("File './transposed_logs.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +# Ready to start creating plots + +mpl.rcParams['axes.labelsize'] = 18 + +# bar plot of actual run times +ax = df.plot.bar(figsize=(10, 7), + logy=True, + ylabel="time (s)", + fontsize=14, + width=0.8, + rot=0) +plt.savefig('../timeit_bar_plot.svg') + +# log-log plot of times vs n, to see slopes +transposed.plot(figsize=(8, 6), + marker='.', + markersize=10, + ylabel="$log_{10}(time)$ (s)", + xlabel="$log_{10}(n)$", + fontsize=14) +plt.savefig('../slopes.svg') diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/fit_gradients.py b/exercises/practice/pythagorean-triplet/.articles/performance/code/fit_gradients.py new file mode 100644 index 0000000000..22ace93f81 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.articles/performance/code/fit_gradients.py @@ -0,0 +1,38 @@ +import pandas as pd +import numpy as np +from numpy.linalg import lstsq + + +# These dataframes are slow to create, so they should be saved in Feather format + +try: + transposed = pd.read_feather('./transposed_logs.feather') +except FileNotFoundError: + print("File './transposed_logs.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +n_values = (12, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000) +log_n_values = np.log10(n_values) +row_headers = ["cubic", "quad_loose", "quad_tight", "linear_loop", "linear_comp"] + + +# Do a least-squares fit to get the slopes, working around missing values +# Apparently, it does need to be this complicated + +def find_slope(name): + log_times = transposed[name] + missing = np.isnan(log_times) + log_times = log_times[~missing] + valid_entries = len(log_times) + A = np.vstack([log_n_values[:valid_entries], np.ones(valid_entries)]).T + m, _ = lstsq(A, log_times, rcond=None)[0] + return m + + +# Print the slope results +slopes = [(name, find_slope(name)) for name in row_headers] +print('\nSlopes of log-log plots:') +for name, slope in slopes: + print(f'{name:>14} : {slope:.2f}') + diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/run_times.feather b/exercises/practice/pythagorean-triplet/.articles/performance/code/run_times.feather new file mode 100644 index 0000000000000000000000000000000000000000..6e93c9c3705b8846b936ce53a60118d5d2c7a26a GIT binary patch literal 5426 zcmeHLVQ5=b6uxcKHk4RXXr-NHb`O;aZI-mF%rf4~(1}y0tZs9-ZrN+{+NHj{#5Ac5 zAubk#{u?p}YjwrxkI7b8Q$a-bV?VTRE|oH!PMrz^#W}H?18u=s&$;j3G<|ugINu+k z7w)_7ob#RY-FtKIx$kCEXXn$;dI(v7@_r>DPJ*S1RFeu)PVDdorNoxgJ3zEC{cGj; z4rZvRx5E={cKWWzs(|;1tOewLLiPYYDFn5EGU)S%)p$tj7do1D3qGH&QM2HQALi0qcriT!S1LW zO<>-MB;;r`{334o0VUcW3~Q+Mw7T3bj{tdrv*#IGT^`qZAqBY>^Yw?2kcXjmL3Lwt zhB^byn@}TAy-+c4$NpMa0Ao4k{IyI64XJ@zkdvw1M*YThzcTczr+Rom!CbHZHiV2( zOewik1Pez2q3Z_PPZ-t)XAtzKATDBQTxfr1Sa%UDjtk>LLj;N`C0)y4F)m`CV%Xt4 zY%S<99+onw5SI)v+{7|Ch)Kp6?ott4DUC`SbPIlhvbI}M`-VgNi zw88_hh{ri6q?DfDyTFKj#^QiqMnAzFG$JXp5sc%+fw1BLjT0i{xsB)dAgmL_U<{md zUhr2({yMHOX(guf8f;wV_O!vyWo|!XuydK)>BmMs2G=(fZuc@7*Cm!+2Ky0%eZpYB zZm`>N!lCf^k1?6Y-^b*39Y&5YnTa`h9aEfT_RSmsE$-R;`9_mfG8;}V-apBFc^LUI zKVY#5TkH3Ehd@k^j*N_C?AOE|C{v$b5K(S8mk}qA&K~bkj*3_B_t?i1$3*I9;*a|8 z?prHOXR}$`+&qo*>)wmvSD*gz%+0d5#S=F!Z2fujWKn;h9eA!WB?0ez?H6tdWM4M_|GK2?pW;w{~96v2Ck6$y6<0-+V_62W%pY@h_77Rw&CQ#KXrdT?`-}+Os~cX zC^9dE%pI7{UVh;8-7`2pJMY;zDW#`6KD4wcl&-lntuM^JhFL(SZNe-`Q8b& z0qVU_`S#1bFuV(FH;6yp9rV8tmjgaE9PU@D$V4pIyFaD|HARlzMb1R@H@LKfBH(}p z()I$jcm_HkV`4UTfP{Gzv$YqVI3~=S_#2&tyA>9mFY@!SZoy|2^V>Gzud)BWlvIKMa1|4RP`vykWi literal 0 HcmV?d00001 diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/transposed_logs.feather b/exercises/practice/pythagorean-triplet/.articles/performance/code/transposed_logs.feather new file mode 100644 index 0000000000000000000000000000000000000000..aacb2e1c55fe3425a528b60980a3bd4c3ab15631 GIT binary patch literal 4010 zcmeHKZ%kWN6u+YdI;dU89|1?SdoFQq$ObMr8A>r|SO%HEjM;E`Jla>*<@LQ3+NjCjprrFFKb6M07A#pUQ!E^4r4_YW$V%P^0 zZgSs!=bYa?=ib|MfA6%Su5S11MTE>k8%QR^POzmB8%ZI_!~$>7R+;DYRuIixpKr!@ zFhfVZ1)gZLuy@^83cULz)h)FUa>qnShu~G+a=Yl#l#owt5lS5mf+*@bMQ)W9ajPf_ z`wJX`j?hgq19k|i>f%yyx(bAB=Ls}NkFCa()!#4TiAycrzrqMMcwE*mmM$#@il7;Gnnp06VV)f#+zdtMm0$Lj$ ze5)L2@oFk+i;4@k78VJRm`3;*V{u_o;WnWYt~{^@Ar2sh8!(=M9)#|Mj(lMOt^w9C zA=hl+IvB`W=!>`jbSVsO@DdBkkgYlSJ#KZ9eOW=_M*#%>H zPRKEizxE(JyR3v<=6E~F0&Ywpsf;b{oKy94h}{p+Fk##RSj6L55|YH8;A-|=IM*yD zA@Amb9hut7yb_a{ps!vX&QMm+*cmhHn0uf_4Ek8j5c?lXc@-u`BitDb3F6MqTNlNJWps5H_m!@9 zXVaTw7pI4n40^6He{Af6mEQSzWA!9Srair*{rTh3k%sEWXY2*zBVotT`9SlHa!-9_ zS55Q%a@{>&#D9qY{h3pH)9>cc3ERorH#cX~q|Z*aP7K?r@cw>hWNBF^%tRH zfA$xL8m2}*-1pJTk^0X^wp?2`_)YC^@$na&EAH>ai*2@CEswgsq?>yG&ZV}j%Irf| za_DH)_2)1AnoUo2dY|4jl}USsYfW{w4C-oZk$;G;p&#rzU42-v(t~duyL$e;RLb_5 z!2j)F`jZYJkCqNp_{=}%&;v&=AO7jtN9ldf8{tpNvZ&x_vq>*y(6dJd``+1Sr|s5H zcXW4K>5L~(=)Yv4yX2C#sxosz|1Ut$sierq<45Z&C(9#$_W!bTa2kLAJ|~^|YHGu` zCg&QGX|a>cbUXROWOp|H;|}-TTkB+tC0Tsb>p0XQw_4$bBZF8$((kmHl!1ODbi5CE z2hJkdK1pNB Date: Mon, 22 Jan 2024 12:19:33 -0800 Subject: [PATCH 589/826] [Currency Exchange]: Update stub & exemplar with Module Docstring (#3600) * [Currency Exchange]: Update stub with Module Docstring Sub file was missing a docstring and tripping a "missing module docstring" warning in the Analyzer. * [Currency Exchange]: Update exemplar.py Added same docstring to exemplar file that was added to the stub file. * Update exchange.py removed numeric stack doc * Update exemplar.py Removed numeric stack doc. [no important files changed] --- exercises/concept/currency-exchange/.meta/exemplar.py | 7 +++++++ exercises/concept/currency-exchange/exchange.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/exercises/concept/currency-exchange/.meta/exemplar.py b/exercises/concept/currency-exchange/.meta/exemplar.py index e452818f24..1cd9b2262e 100644 --- a/exercises/concept/currency-exchange/.meta/exemplar.py +++ b/exercises/concept/currency-exchange/.meta/exemplar.py @@ -1,3 +1,10 @@ +"""Functions for calculating steps in exchaning currency. + +Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex + +Overview of exchanging currency when travelling: https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ +""" + def exchange_money(budget, exchange_rate): """ diff --git a/exercises/concept/currency-exchange/exchange.py b/exercises/concept/currency-exchange/exchange.py index e05c7bcc54..c7088f6183 100644 --- a/exercises/concept/currency-exchange/exchange.py +++ b/exercises/concept/currency-exchange/exchange.py @@ -1,3 +1,12 @@ +"""Functions for calculating steps in exchaning currency. + +Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex + +Overview of exchanging currency when travelling: https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ +""" + + + def exchange_money(budget, exchange_rate): """ From 7fad60db57a39194ff06129dbae80b96d1cacaab Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 22 Jan 2024 14:55:52 -0800 Subject: [PATCH 590/826] [Python Docs]: Update Python Version in INSTALLATION.md (#3601) Changed version from Python `3.11.2` to `3.11.5` --- docs/INSTALLATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 04f9c89934..7be6910710 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -18,7 +18,7 @@ Some quick links into the documentation by operating system: We recommend reviewing some of the methods outlined in the Real Python article [Installing Python][installing-python] or the articles by Brett Cannon linked above. -Exercism tests and tooling currently support `3.7` - `3.11.2` (_tests_) and [`Python 3.11.2`][311-new-features] (_tooling_). +Exercism tests and tooling currently support `3.7` - `3.11.5` (_tests_) and [`Python 3.11.5`][311-new-features] (_tooling_). Exceptions to this support are noted where they occur. Most of the exercises will work with `Python 3.6+`, or even earlier versions. But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases]. From ac97b5e01e77f3714b1020f6c9357c72cbef3145 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 24 Jan 2024 13:28:14 -0800 Subject: [PATCH 591/826] [Parallel Letter Frequency]: Mark as Foregone in Config.json and Remove Exercise Directory (#3603) * Marked parallel-letter-frequency as foregone. * Removed string-formattig prerequisites and set dict-methods live to students. * Added parallel-letter-frequency back into depricated array. * Removed parallel-letter-frequency exercise directory. --- config.json | 15 +---- .../.docs/instructions.md | 7 --- .../.meta/config.json | 25 -------- .../.meta/example.py | 51 --------------- .../.meta/tests.toml | 62 ------------------- .../parallel_letter_frequency.py | 2 - .../parallel_letter_frequency_test.py | 61 ------------------ 7 files changed, 2 insertions(+), 221 deletions(-) delete mode 100644 exercises/practice/parallel-letter-frequency/.docs/instructions.md delete mode 100644 exercises/practice/parallel-letter-frequency/.meta/config.json delete mode 100644 exercises/practice/parallel-letter-frequency/.meta/example.py delete mode 100644 exercises/practice/parallel-letter-frequency/.meta/tests.toml delete mode 100644 exercises/practice/parallel-letter-frequency/parallel_letter_frequency.py delete mode 100644 exercises/practice/parallel-letter-frequency/parallel_letter_frequency_test.py diff --git a/config.json b/config.json index 6afd504680..754ca9f3fd 100644 --- a/config.json +++ b/config.json @@ -150,7 +150,7 @@ "uuid": "5ac0c40c-4038-47b8-945b-8480e4a3f44c", "concepts": ["dict-methods"], "prerequisites": ["dicts"], - "status": "wip" + "status": "beta" }, { "slug": "locomotive-engineer", @@ -206,7 +206,6 @@ "comprehensions", "loops", "sequences", - "string-formatting", "string-methods", "tuples" ], @@ -921,7 +920,6 @@ "loops", "numbers", "strings", - "string-formatting", "string-methods" ], "difficulty": 2 @@ -2190,15 +2188,6 @@ "difficulty": 3, "status": "deprecated" }, - { - "slug": "parallel-letter-frequency", - "name": "Parallel Letter Frequency", - "uuid": "da03fca4-4606-48d8-9137-6e40396f7759", - "practices": [], - "prerequisites": [], - "difficulty": 3, - "status": "deprecated" - }, { "slug": "point-mutations", "name": "Point Mutations", @@ -2245,7 +2234,7 @@ "status": "deprecated" } ], - "foregone": ["lens-person"] + "foregone": ["lens-person", "parallel-letter-frequency"] }, "concepts": [ { diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md deleted file mode 100644 index 85abcf86a4..0000000000 --- a/exercises/practice/parallel-letter-frequency/.docs/instructions.md +++ /dev/null @@ -1,7 +0,0 @@ -# Instructions - -Count the frequency of letters in texts using parallel computation. - -Parallelism is about doing things in parallel that can also be done sequentially. -A common example is counting the frequency of letters. -Create a function that returns the total frequency of each letter in a list of texts and that employs parallelism. diff --git a/exercises/practice/parallel-letter-frequency/.meta/config.json b/exercises/practice/parallel-letter-frequency/.meta/config.json deleted file mode 100644 index 3945e3f232..0000000000 --- a/exercises/practice/parallel-letter-frequency/.meta/config.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "blurb": "Count the frequency of letters in texts using parallel computation.", - "authors": [ - "forgeRW" - ], - "contributors": [ - "behrtam", - "cmccandless", - "Dog", - "kytrinyx", - "N-Parsons", - "tqa236" - ], - "files": { - "solution": [ - "parallel_letter_frequency.py" - ], - "test": [ - "parallel_letter_frequency_test.py" - ], - "example": [ - ".meta/example.py" - ] - } -} diff --git a/exercises/practice/parallel-letter-frequency/.meta/example.py b/exercises/practice/parallel-letter-frequency/.meta/example.py deleted file mode 100644 index 5a16fb31c1..0000000000 --- a/exercises/practice/parallel-letter-frequency/.meta/example.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -from collections import Counter -from threading import Lock, Thread -from time import sleep -from queue import Queue - - -TOTAL_WORKERS = 3 # Maximum number of threads chosen arbitrarily - -class LetterCounter: - - def __init__(self): - self.lock = Lock() - self.value = Counter() - - def add_counter(self, counter_to_add): - self.lock.acquire() - try: - self.value = self.value + counter_to_add - finally: - self.lock.release() - - -def count_letters(queue_of_texts, letter_to_frequency, worker_id): - while not queue_of_texts.empty(): - sleep(worker_id + 1) - line_input = queue_of_texts.get() - if line_input is not None: - letters_in_line = Counter(idx for idx in line_input.lower() if idx.isalpha()) - letter_to_frequency.add_counter(letters_in_line) - queue_of_texts.task_done() - if line_input is None: - break - - -def calculate(list_of_texts): - queue_of_texts = Queue() - for line in list_of_texts: - queue_of_texts.put(line) - letter_to_frequency = LetterCounter() - threads = [] - for idx in range(TOTAL_WORKERS): - worker = Thread(target=count_letters, args=(queue_of_texts, letter_to_frequency, idx)) - worker.start() - threads.append(worker) - queue_of_texts.join() - for _ in range(TOTAL_WORKERS): - queue_of_texts.put(None) - for thread in threads: - thread.join() - return letter_to_frequency.value diff --git a/exercises/practice/parallel-letter-frequency/.meta/tests.toml b/exercises/practice/parallel-letter-frequency/.meta/tests.toml deleted file mode 100644 index 6cf36e6fd2..0000000000 --- a/exercises/practice/parallel-letter-frequency/.meta/tests.toml +++ /dev/null @@ -1,62 +0,0 @@ -# This is an auto-generated file. -# -# Regenerating this file via `configlet sync` will: -# - Recreate every `description` key/value pair -# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications -# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) -# - Preserve any other key/value pair -# -# As user-added comments (using the # character) will be removed when this file -# is regenerated, comments can be added via a `comment` key. - -[c054d642-c1fa-4234-8007-9339f2337886] -description = "no texts" -include = false - -[818031be-49dc-4675-b2f9-c4047f638a2a] -description = "one text with one letter" -include = false - -[c0b81d1b-940d-4cea-9f49-8445c69c17ae] -description = "one text with multiple letters" -include = false - -[708ff1e0-f14a-43fd-adb5-e76750dcf108] -description = "two texts with one letter" -include = false - -[1b5c28bb-4619-4c9d-8db9-a4bb9c3bdca0] -description = "two texts with multiple letters" -include = false - -[6366e2b8-b84c-4334-a047-03a00a656d63] -description = "ignore letter casing" -include = false - -[92ebcbb0-9181-4421-a784-f6f5aa79f75b] -description = "ignore whitespace" -include = false - -[bc5f4203-00ce-4acc-a5fa-f7b865376fd9] -description = "ignore punctuation" -include = false - -[68032b8b-346b-4389-a380-e397618f6831] -description = "ignore numbers" -include = false - -[aa9f97ac-3961-4af1-88e7-6efed1bfddfd] -description = "Unicode letters" -include = false - -[7b1da046-701b-41fc-813e-dcfb5ee51813] -description = "combination of lower- and uppercase letters, punctuation and white space" -include = false - -[4727f020-df62-4dcf-99b2-a6e58319cb4f] -description = "large texts" -include = false - -[adf8e57b-8e54-4483-b6b8-8b32c115884c] -description = "many small texts" -include = false diff --git a/exercises/practice/parallel-letter-frequency/parallel_letter_frequency.py b/exercises/practice/parallel-letter-frequency/parallel_letter_frequency.py deleted file mode 100644 index 1b9e1f7f9c..0000000000 --- a/exercises/practice/parallel-letter-frequency/parallel_letter_frequency.py +++ /dev/null @@ -1,2 +0,0 @@ -def calculate(text_input): - pass diff --git a/exercises/practice/parallel-letter-frequency/parallel_letter_frequency_test.py b/exercises/practice/parallel-letter-frequency/parallel_letter_frequency_test.py deleted file mode 100644 index 23860c7f47..0000000000 --- a/exercises/practice/parallel-letter-frequency/parallel_letter_frequency_test.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -from collections import Counter -import unittest - -from parallel_letter_frequency import calculate - - -class ParallelLetterFrequencyTest(unittest.TestCase): - def test_one_letter(self): - actual = calculate(['a']) - expected = {'a': 1} - self.assertDictEqual(actual, expected) - - def test_case_insensitivity(self): - actual = calculate(['aA']) - expected = {'a': 2} - self.assertDictEqual(actual, expected) - - def test_numbers(self): - actual = calculate(['012', '345', '6789']) - expected = {} - self.assertDictEqual(actual, expected) - - def test_punctuations(self): - actual = calculate([r'[]\;,', './{}|', ':"<>?']) - expected = {} - self.assertDictEqual(actual, expected) - - def test_whitespaces(self): - actual = calculate([' ', '\t ', '\n\n']) - expected = {} - self.assertDictEqual(actual, expected) - - def test_repeated_string_with_known_frequencies(self): - letter_frequency = 3 - text_input = 'abc\n' * letter_frequency - actual = calculate(text_input.split('\n')) - expected = {'a': letter_frequency, 'b': letter_frequency, - 'c': letter_frequency} - self.assertDictEqual(actual, expected) - - def test_multiline_text(self): - text_input = "3 Quotes from Excerism Homepage:\n" + \ - "\tOne moment you feel like you're\n" + \ - "getting it. The next moment you're\n" + \ - "stuck.\n" + \ - "\tYou know what it’s like to be fluent.\n" + \ - "Suddenly you’re feeling incompetent\n" + \ - "and clumsy.\n" + \ - "\tHaphazard, convoluted code is\n" + \ - "infuriating, not to mention costly. That\n" + \ - "slapdash explosion of complexity is an\n" + \ - "expensive yak shave waiting to\n" + \ - "happen." - actual = calculate(text_input.split('\n')) - expected = Counter([x for x in text_input.lower() if x.isalpha()]) - self.assertDictEqual(actual, expected) - - -if __name__ == '__main__': - unittest.main() From 70acb845ed48d3a428666c6ef4accb799208d3e2 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Thu, 25 Jan 2024 14:09:26 +0000 Subject: [PATCH 592/826] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/42a096591ac4a748b6c4daf402d46c7eed640a1a --- .../workflows/no-important-files-changed.yml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/no-important-files-changed.yml diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml new file mode 100644 index 0000000000..39a6551747 --- /dev/null +++ b/.github/workflows/no-important-files-changed.yml @@ -0,0 +1,67 @@ +name: No important files changed + +on: + pull_request: + types: [opened] + branches: [main] + +permissions: + pull-requests: write + +jobs: + no_important_files_changed: + name: No important files changed + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Check if important files changed + id: check + run: | + set -exo pipefail + + # fetch a ref to the main branch so we can diff against it + git remote set-branches origin main + git fetch --depth 1 origin main + + for changed_file in $(git diff --diff-filter=M --name-only origin/main); do + if ! echo "$changed_file" | grep --quiet --extended-regexp 'exercises/(practice|concept)' ; then + continue + fi + slug="$(echo "$changed_file" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" + path_before_slug="$(echo "$changed_file" | sed --regexp-extended "s#(.*)/$slug/.*#\\1#" )" + path_after_slug="$( echo "$changed_file" | sed --regexp-extended "s#.*/$slug/(.*)#\\1#" )" + + if ! [ -f "$path_before_slug/$slug/.meta/config.json" ]; then + # cannot determine if important files changed without .meta/config.json + continue + fi + + # returns 0 if the filter matches, 1 otherwise + # | contains($path_after_slug) + if jq --exit-status \ + --arg path_after_slug "$path_after_slug" \ + '[.files.test, .files.invalidator, .files.editor] | flatten | index($path_after_slug)' \ + "$path_before_slug/$slug/.meta/config.json" \ + > /dev/null; + then + echo "important_files_changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + done + + echo "important_files_changed=false" >> "$GITHUB_OUTPUT" + + - name: Suggest to add [no important files changed] + if: steps.check.outputs.important_files_changed == 'true' + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: | + const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }) From 93278ea72554199a1c4a8fd7737e8bccb21882a9 Mon Sep 17 00:00:00 2001 From: Tomasz Gieorgijewski <24649931+Nerwosolek@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:19:52 +0100 Subject: [PATCH 593/826] Update instructions.md (#3606) A few instructions missed additional parameter in function declaration. Added. --- exercises/concept/making-the-grade/.docs/instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/making-the-grade/.docs/instructions.md b/exercises/concept/making-the-grade/.docs/instructions.md index 2f0c6617ae..f7e0f3a307 100644 --- a/exercises/concept/making-the-grade/.docs/instructions.md +++ b/exercises/concept/making-the-grade/.docs/instructions.md @@ -39,7 +39,7 @@ A student needs a score greater than **40** to achieve a passing grade on the ex The teacher you're assisting wants to find the group of students who've performed "the best" on this exam. What qualifies as "the best" fluctuates, so you need to find the student scores that are **greater than or equal to** the current threshold. -Create the function `above_threshold(student_scores)` taking `student_scores` (a `list` of grades), and `threshold` (the "top score" threshold) as parameters. +Create the function `above_threshold(student_scores, threshold)` taking `student_scores` (a `list` of grades), and `threshold` (the "top score" threshold) as parameters. This function should return a `list` of all scores that are `>=` to `threshold`. ```python @@ -85,7 +85,7 @@ Create the function `letter_grades(highest)` that takes the "highest" score on t You have a list of exam scores in descending order, and another list of student names also sorted in descending order by their exam scores. You would like to match each student name with their exam score and print out an overall class ranking. -Create the function `student_ranking(student_scores)` with parameters `student_scores` and `student_names`. +Create the function `student_ranking(student_scores, student_names)` with parameters `student_scores` and `student_names`. Match each student name on the student_names `list` with their score from the student_scores `list`. You can assume each argument `list` will be sorted from highest score(er) to lowest score(er). The function should return a `list` of strings with the format `. : `. From 7965a4f5b6d722eec0a9e76d3b9dd60336ce0605 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 22:30:55 -0800 Subject: [PATCH 594/826] Bump juliangruber/read-file-action from 1.1.6 to 1.1.7 (#3607) Bumps [juliangruber/read-file-action](https://github.com/juliangruber/read-file-action) from 1.1.6 to 1.1.7. - [Release notes](https://github.com/juliangruber/read-file-action/releases) - [Commits](https://github.com/juliangruber/read-file-action/compare/02bbba9876a8f870efd4ad64e3b9088d3fb94d4b...b549046febe0fe86f8cb4f93c24e284433f9ab58) --- updated-dependencies: - dependency-name: juliangruber/read-file-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue-commenter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index cbe6388366..37a459ce50 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -13,7 +13,7 @@ jobs: - name: Read issue-comment.md id: issue-comment - uses: juliangruber/read-file-action@02bbba9876a8f870efd4ad64e3b9088d3fb94d4b + uses: juliangruber/read-file-action@b549046febe0fe86f8cb4f93c24e284433f9ab58 with: path: .github/issue-comment.md From af8053d8a7115f8092fa1ce09a958d99a3e6b600 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 30 Jan 2024 01:56:01 -0800 Subject: [PATCH 595/826] [Raindrops]: Added Instruction Append for `divmod()`, `math.fmod()`, `math.remainder()`, `%` & `operator.modulo` (#3611) * Added refences for divmod, fmod, remainder, modulo and operator.modulo * Added comma on line 6 for clarity. --- .../raindrops/.docs/instructions.append.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 exercises/practice/raindrops/.docs/instructions.append.md diff --git a/exercises/practice/raindrops/.docs/instructions.append.md b/exercises/practice/raindrops/.docs/instructions.append.md new file mode 100644 index 0000000000..9558c33934 --- /dev/null +++ b/exercises/practice/raindrops/.docs/instructions.append.md @@ -0,0 +1,21 @@ +# Instructions append + +## How this Exercise is Structured in Python + +This exercise is best solved with Python's `%` ([modulo][modulo]) operator, which returns the remainder of positive integer division. +It has a method equivalent, `operator.mod()` in the [operator module][operator-mod]. + + +Python also offers additional 'remainder' methods in the [math module][math-module]. +[`math.fmod()`][fmod] behaves like `%`, but operates on floats. +[`math.remainder()`][remainder] implements a "step closest to zero" algorithm for the remainder of division. +While we encourage you to get familiar with these methods, neither of these will exactly match the result of `%`, and are not recommended for use with this exercise. + +The built-in function [`divmod()`][divmod] will also give a remainder than matches `%` if used with two positive integers, but returns a `tuple` that needs to be unpacked. + +[divmod]: https://docs.python.org/3/library/functions.html#divmod +[fmod]: https://docs.python.org/3/library/math.html#math.fmod +[math-module]: https://docs.python.org/3/library/math.html +[modulo]: https://www.programiz.com/python-programming/operators#arithmetic +[operator-mod]: https://docs.python.org/3/library/operator.html#operator.mod +[remainder]: https://docs.python.org/3/library/math.html#math.remainder From cbbfcfdbf0ff10ad65b61098221a7226f6db3439 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 30 Jan 2024 10:56:42 +0100 Subject: [PATCH 596/826] Sync the `raindrops` exercise's docs with the latest data. (#3610) --- .../practice/raindrops/.docs/instructions.md | 26 +++++++++++-------- .../practice/raindrops/.docs/introduction.md | 3 +++ 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 exercises/practice/raindrops/.docs/introduction.md diff --git a/exercises/practice/raindrops/.docs/instructions.md b/exercises/practice/raindrops/.docs/instructions.md index fc61d36e99..df64410751 100644 --- a/exercises/practice/raindrops/.docs/instructions.md +++ b/exercises/practice/raindrops/.docs/instructions.md @@ -1,20 +1,24 @@ # Instructions -Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. -A factor is a number that evenly divides into another number, leaving no remainder. -The simplest way to test if one number is a factor of another is to use the [modulo operation][modulo]. +Your task is to convert a number into its corresponding raindrop sounds. -The rules of `raindrops` are that if a given number: +If a given number: -- has 3 as a factor, add 'Pling' to the result. -- has 5 as a factor, add 'Plang' to the result. -- has 7 as a factor, add 'Plong' to the result. -- _does not_ have any of 3, 5, or 7 as a factor, the result should be the digits of the number. +- is divisible by 3, add "Pling" to the result. +- is divisible by 5, add "Plang" to the result. +- is divisible by 7, add "Plong" to the result. +- **is not** divisible by 3, 5, or 7, the result should be the number as a string. ## Examples -- 28 has 7 as a factor, but not 3 or 5, so the result would be "Plong". -- 30 has both 3 and 5 as factors, but not 7, so the result would be "PlingPlang". -- 34 is not factored by 3, 5, or 7, so the result would be "34". +- 28 is divisible by 7, but not 3 or 5, so the result would be `"Plong"`. +- 30 is divisible by 3 and 5, but not 7, so the result would be `"PlingPlang"`. +- 34 is not divisible by 3, 5, or 7, so the result would be `"34"`. +~~~~exercism/note +A common way to test if one number is evenly divisible by another is to compare the [remainder][remainder] or [modulus][modulo] to zero. +Most languages provide operators or functions for one (or both) of these. + +[remainder]: https://exercism.org/docs/programming/operators/remainder [modulo]: https://en.wikipedia.org/wiki/Modulo_operation +~~~~ diff --git a/exercises/practice/raindrops/.docs/introduction.md b/exercises/practice/raindrops/.docs/introduction.md new file mode 100644 index 0000000000..ba12100f3b --- /dev/null +++ b/exercises/practice/raindrops/.docs/introduction.md @@ -0,0 +1,3 @@ +# Introduction + +Raindrops is a slightly more complex version of the FizzBuzz challenge, a classic interview question. From 4e698334dc52ca14e63d727d55e7dac59622b41b Mon Sep 17 00:00:00 2001 From: colinleach Date: Wed, 31 Jan 2024 03:13:46 -0700 Subject: [PATCH 597/826] [Roman Numerals] Draft of Approaches (#3605) * [Roman Numerals] early draft of approaches * updated approach documents, many changes * fixed typos and other glitches * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/recurse-match/content.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/recurse-match/content.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/recurse-match/content.md Co-authored-by: BethanyG * Fixed TODO in `itertools-starmap` * Update content.md Found a mistake already * Update content.md again * Update exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md * Update exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md --------- Co-authored-by: BethanyG --- .../roman-numerals/.approaches/config.json | 60 +++++ .../.approaches/if-else/content.md | 103 +++++++++ .../.approaches/if-else/snippet.txt | 8 + .../.approaches/introduction.md | 205 ++++++++++++++++++ .../.approaches/itertools-starmap/content.md | 90 ++++++++ .../.approaches/itertools-starmap/snippet.txt | 7 + .../.approaches/loop-over-romans/content.md | 125 +++++++++++ .../.approaches/loop-over-romans/snippet.txt | 8 + .../.approaches/recurse-match/content.md | 57 +++++ .../.approaches/recurse-match/snippet.txt | 8 + .../.approaches/table-lookup/content.md | 68 ++++++ .../.approaches/table-lookup/snippet.txt | 8 + 12 files changed, 747 insertions(+) create mode 100644 exercises/practice/roman-numerals/.approaches/config.json create mode 100644 exercises/practice/roman-numerals/.approaches/if-else/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/if-else/snippet.txt create mode 100644 exercises/practice/roman-numerals/.approaches/introduction.md create mode 100644 exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/itertools-starmap/snippet.txt create mode 100644 exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt create mode 100644 exercises/practice/roman-numerals/.approaches/recurse-match/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/recurse-match/snippet.txt create mode 100644 exercises/practice/roman-numerals/.approaches/table-lookup/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt diff --git a/exercises/practice/roman-numerals/.approaches/config.json b/exercises/practice/roman-numerals/.approaches/config.json new file mode 100644 index 0000000000..d824f6dd80 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/config.json @@ -0,0 +1,60 @@ +{ + "introduction": { + "authors": [ + "colinleach", + "BethanyG" + ] + }, + "approaches": [ + { + "uuid": "69c84b6b-5e58-4b92-a2c4-17dd7d353087", + "slug": "if-else", + "title": "If Else", + "blurb": "Use booleans to find the correct translation for each digit.", + "authors": [ + "BethanyG", + "colinleach" + ] + }, + { + "uuid": "4ed8396c-f2c4-4072-abc9-cc8fe2780a5a", + "slug": "loop-over-romans", + "title": "Loop Over Romans", + "blurb": "Test Roman Numerals from the largest down and eat the maximum possible at each step.", + "authors": [ + "BethanyG", + "colinleach" + ] + }, + { + "uuid": "ba022b7e-5ea8-4432-ab94-6e9be200a70b", + "slug": "table-lookup", + "title": "Table Lookup", + "blurb": "Use a 2-D lookup table to eliminate loop nesting.", + "authors": [ + "BethanyG", + "colinleach" + ] + }, + { + "uuid": "3d6df007-455f-4210-922b-63d5a24bfaf8", + "slug": "itertools-starmap", + "title": "Itertools Starmap", + "blurb": "Use itertools.starmap() for an ingenious functional approach.", + "authors": [ + "BethanyG", + "colinleach" + ] + }, + { + "uuid": "a492c6b4-3780-473d-a2e6-a1c3e3da4f81", + "slug": "recurse-match", + "title": "Recurse Match", + "blurb": "Combine recursive programming with the recently-introduced structural pattern matching.", + "authors": [ + "BethanyG", + "colinleach" + ] + } + ] +} diff --git a/exercises/practice/roman-numerals/.approaches/if-else/content.md b/exercises/practice/roman-numerals/.approaches/if-else/content.md new file mode 100644 index 0000000000..798075f1fe --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/if-else/content.md @@ -0,0 +1,103 @@ +# If Else + +```python +def roman(number): + # The notation: I, V, X, L, C, D, M = 1, 5, 10, 50, 100, 500, 1000 + m = number // 1000 + m_rem = number % 1000 + c = m_rem // 100 + c_rem = m_rem % 100 + x = c_rem // 10 + x_rem = c_rem % 10 + i = x_rem + + res = '' + + if m > 0: + res += m * 'M' + + if 4 > c > 0: + res += c * 'C' + elif c == 4: + res += 'CD' + elif 9 > c > 4: + res += 'D' + ((c - 5) * 'C') + elif c == 9: + res += 'CM' + + if 4 > x > 0: + res += x * 'X' + elif x == 4: + res += 'XL' + elif 9 > x > 4: + res += 'L' + ((x - 5) * 'X') + elif x == 9: + res += 'XC' + + if 4 > i > 0: + res += i * 'I' + elif i == 4: + res += 'IV' + elif 9 > i > 4: + res += 'V' + ((i - 5) * 'I') + elif i == 9: + res += 'IX' + + return res +``` + +This gets the job done. +Something like it would work in most languages, though Python's range test (`a > x > b`) saves some boolean logic. + +## Refactoring + +The code above is quite long and a bit repetitive. +We should explore ways to make it more concise. + +The first block is just a way to extract the digits from the input number. +This can be done with a list comprehension, left-padding with zeros as necessary: + +```python +digits = ([0, 0, 0, 0] + [int(d) for d in str(number)])[-4:] +``` + +The blocks for hundreds, tens and units are all essentially the same, so we can put that code in a function. +We just need to pass in the digit, plus a tuple of translations for `(1, 4, 5, 9)` or their 10x and 100x equivalents. + +It is also unnecessary to keep retesting the lower bounds within an `elif`, as the code line will only be reached if that is satisfied. + +Using `return` instead of `elif` is a matter of personal preference. +Given that, the code simplifies to: + +```python +def roman(number: int) -> str: + def translate_digit(digit: int, translations: iter) -> str: + assert isinstance(digit, int) and 0 <= digit <= 9 + + units, four, five, nine = translations + if digit < 4: + return digit * units + if digit == 4: + return four + if digit < 9: + return five + (digit - 5) * units + return nine + + assert isinstance(number, int) + m, c, x, i = ([0, 0, 0, 0] + [int(d) for d in str(number)])[-4:] + res = '' + + if m > 0: + res += m * 'M' + if c > 0: + res += translate_digit(c, ('C', 'CD', 'D', 'CM')) + if x > 0: + res += translate_digit(x, ('X', 'XL', 'L', 'XC')) + if i > 0: + res += translate_digit(i, ('I', 'IV', 'V', 'IX')) + + return res +``` + +The last few lines are quite similar and it would be possible to refactor them into a loop, but this is enough to illustrate the principle. + diff --git a/exercises/practice/roman-numerals/.approaches/if-else/snippet.txt b/exercises/practice/roman-numerals/.approaches/if-else/snippet.txt new file mode 100644 index 0000000000..829bb41dd2 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/if-else/snippet.txt @@ -0,0 +1,8 @@ + def translate_digit(digit: int, translations: iter) -> str: + units, four, five, nine = translations + if digit < 4: return digit * units + if digit == 4: return four + if digit < 9: return five + (digit - 5) * units + return nine + + if c > 0: res += translate_digit(c, ('C', 'CD', 'D', 'CM')) diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md new file mode 100644 index 0000000000..4468862f0c --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -0,0 +1,205 @@ +# Introduction + +There is no single, obvious solution to this exercise, but a diverse array of working solutions have been used. + +## General guidance + +Roman numerals are limited to positive integers from 1 to 3999 (MMMCMXCIX). +In the version used for this exercise, the longest string needed to represent a Roman numeral is 14 characters (MMDCCCLXXXVIII). +Minor variants of the system have been used which represent 4 as IIII rather than IV, allowing for longer strings, but those are not relevant here. + +The system is inherently decimal: the number of human fingers has not changed since ancient Rome, nor the habit of using them for counting. +However, there is no zero value available, so Roman numerals represent powers of 10 with different letters (I, X, C, M), not by position (1, 10, 100, 1000). + +The approaches to this exercise break down into two groups, with many variants in each: +1. Split the input number into digits, and translate each separately. +2. Iterate through the Roman numbers, from large to small, and convert the largest valid number at each step. + +## Digit-by-digit approaches + +The concept behind this class of approaches: +1. Split the input number into decimal digits. +2. For each digit, get the Roman equivalent and append to a list. +3. Join the list into a string and return it. +Depending on the implementation, there may need to be a list-reverse step. + +### With `if` conditions + +```python +def roman(number: int) -> str: + assert isinstance(number, int) + + def translate_digit(digit: int, translations: iter) -> str: + assert isinstance(digit, int) and 0 <= digit <= 9 + + units, four, five, nine = translations + if digit < 4: + return digit * units + if digit == 4: + return four + if digit < 9: + return five + (digit - 5) * units + return nine + + m, c, x, i = ([0, 0, 0, 0] + [int(d) for d in str(number)])[-4:] + res = '' + if m > 0: + res += m * 'M' + if c > 0: + res += translate_digit(c, ('C', 'CD', 'D', 'CM')) + if x > 0: + res += translate_digit(x, ('X', 'XL', 'L', 'XC')) + if i > 0: + res += translate_digit(i, ('I', 'IV', 'V', 'IX')) + return res +``` + +See [`if-else`][if-else] for details. + +### With table lookup + +```python +def roman(number): + assert (number > 0) + + # define lookup table (as a tuple of tuples, in this case) + table = ( + ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), + ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), + ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), + ("M", "MM", "MMM")) + + # convert the input integer to a list of single digits + digits = [int(d) for d in str(number)] + + # we need the row in the lookup table for our most-significant decimal digit + inverter = len(digits) - 1 + + # translate decimal digits list to Roman numerals list + roman_digits = [table[inverter - i][d - 1] for (i, d) in enumerate(digits) if d != 0] + + # convert the list of Roman numerals to a single string + return ''.join(roman_digits) +``` + +See [`table-lookup`][table-lookup] for details. + + +## Loop over Romans approaches + +In this class of approaches we: +1. Create a mapping from Roman to Arabic numbers, in some suitable format. (_`dicts` or `tuples` work well_) +2. Iterate nested loops, a `for` and a `while`, in either order. +3. At each step, append the largest possible Roman number to a list and subtract the corresponding value from the number being converted. +4. When the number being converted drops to zero, join the list into a string and return it. +Depending on the implementation, there may need to be a list-reverse step. + +This is one example using a dictionary: + +```python +ROMAN = {1000: 'M', 900: 'CM', 500: 'D', 400: 'CD', + 100: 'C', 90: 'XC', 50: 'L', 40: 'XL', + 10: 'X', 9: 'IX', 5: 'V', 4: 'IV', 1: 'I'} + +def roman(number: int) -> str: + result = '' + while number: + for arabic in ROMAN.keys(): + if number >= arabic: + result += ROMAN[arabic] + number -= arabic + break + return result +``` + +There are a number of variants. +See [`loop-over-romans`][loop-over-romans] for details. + +## Other approaches + +### Built-in methods + +Python has a package for pretty much everything, and Roman numerals are [no exception][roman-module]. + +```python +>>> import roman +>>> roman.toRoman(23) +'XXIII' +>>> roman.fromRoman('MMDCCCLXXXVIII') +2888 +``` + +First it is necessary to install the package with `pip` or `conda`. +Like most external packages, `roman` is not available in the Exercism test runner. + +This is the key part of the implementation on GitHub, which may look familiar: + +```python +def toRoman(n): + result = "" + for numeral, integer in romanNumeralMap: + while n >= integer: + result += numeral + n -= integer + return result +``` + +The library function is a wrapper around a `loop-over-romans` approach! + +### Recursion + +This is a recursive version of the `loop-over-romans` approach, which only works in Python 3.10 and later: + +```python +ARABIC_NUM = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) +ROMAN_NUM = ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") + +def roman(number: int) -> str: + return roman_recur(number, 0, []) + +def roman_recur(num: int, idx: int, digits: list[str]): + match (num, idx, digits): + case [_, 13, digits]: + return ''.join(digits[::-1]) + case [num, idx, digits] if num >= ARABIC_NUM[idx]: + return roman_recur(num - ARABIC_NUM[idx], idx, [ROMAN_NUM[idx],] + digits) + case [num, idx, digits]: + return roman_recur(num, idx + 1, digits) +``` + +See [`recurse-match`][recurse-match] for details. + + +### Over-use a functional approach + +```python +def roman(number): + return ''.join(one*digit if digit<4 else one+five if digit==4 else five+one*(digit-5) if digit<9 else one+ten + for digit, (one,five,ten) + in zip([int(d) for d in str(number)], ["--MDCLXVI"[-i*2-1:-i*2-4:-1] for i in range(len(str(number))-1,-1,-1)])) +``` + +*This is Python, but not as we know it*. + +As the textbooks say, further analysis of this approach is left as an exercise for the reader. + +## Which approach to use? + +In production, it would make sense to use the `roman` package. +It is debugged and supports Roman-to-Arabic conversions in addtion to the Arabic-to-Roman approaches discussed here. + +Most submissions, like the `roman` package implementation, use some variant of [`loop-over-romans`][loop-over-romans]. + +Using a [2-D lookup table][table-lookup] takes a bit more initialization, but then everthing can be done in a list comprehension instead of nested loops. +Python is relatively unusual in supporting both tuples-of-tuples and relatively fast list comprehensions, so the approach seems a good fit for this language. + +No performance article is currently included for this exercise. +The problem is inherently limited in scope by the design of Roman numerals, so any of the approaches is likely to be "fast enough". + + + +[if-else]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/if-else +[table-lookup]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/table-lookup +[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman +[recurse-match]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/recurse-match +[roman-module]: https://github.com/zopefoundation/roman diff --git a/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md b/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md new file mode 100644 index 0000000000..95b820ec1b --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md @@ -0,0 +1,90 @@ +# With `itertools.starmap()` + +```python +from itertools import starmap + +def roman(number: int) -> str: + orders = [(1000, "M "), (100, "CDM"), (10, "XLC"), (1, "IVX")] + options = lambda I, V, X: ["", I, I * 2, I * 3, I + V, V, V + I, V + I * 2, V + I * 3, I + X] + compute = lambda n, chars: options(*chars)[number % (n * 10) // n] + return "".join(starmap(compute, orders)) +``` + +This approach is certainly concise and ingenious, though it takes functional programming to a level that some Python programmers might consider a little cryptic. + +The [`itertools.starmap()`][starmap] method is a variant of `map()` that takes its argument parameters pre-zipped in tuples. +It has the signature `starmap(f: function, i: iter) -> iter`. + +## Linting + +One issue with this code is the use of named lambdas. +This is discouraged by [PEP-8][pep8], and linters will complain about it. + +The underlying reason is that lambdas are intended to be anonymous functions embedded within other expressions. +Internally, they are all given the same name ``, which can greatly complicate debugging. +Their use is a particular bugbear of the Python track maintainer. + +We can refactor the code to satisfy the linter by using named `def` statements, and lowercase argument names. +Type hints are also added for documentation: + +```python +from itertools import starmap + + +def roman(number: int) -> str: + def options(i: str, v: str, x: str): + return ["", i, i * 2, i * 3, i + v, v, v + i, v + i * 2, v + i * 3, i + x] + + def compute(n: int, chars: str) -> iter: + return options(*chars)[number % (n * 10) // n] + + orders = [(1000, "M "), (100, "CDM"), (10, "XLC"), (1, "IVX")] + return "".join(starmap(compute, orders)) +``` + +## Analysis + +The central concept is that Roman letters are defined for 1, 5 and 10, times various powers of 10. + +`orders` is relatively straightforward: a list of tuples, with each tuple containing the powers of 10 and (as far as possible) the letters for that number times (1, 5, 10). +Roman numerals for 5,000 and 10,000 are not defined, so are replaced here by spaces. + +The `options()` function just takes the three letters from one of these tuples and returns a list of numerals that can be constructed from them. +For example, the 10 to 90 range: + +```python +options('X', 'L', 'C') +# => ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'] +``` + +There is no zero, so that is replaced by an empty string. + +The `compute()` function takes a tuple from `orders` plus the top-level parameter `number`, and converts the appropriate decimal digit to its Roman equivalent, returning an `iterator`. +For example the first digit of 723: + +```python +number = 723 +[x for x in compute(100, "CDM")] +# => ['D', 'C', 'C'] +``` + +The `starmap()` function ties `orders`, `options()` and `compute` together, splitting up strings and tuples as necessary to give each function the parameters it needs. +Again, an iterator is returned: + +```python +number = 723 +[x for x in starmap(compute, orders)] +# => ['', 'DCC', 'XX', 'III'] +``` + +Finally, `''.join()` converts this iterator to a single string that can be returned as the desired answer. + +Once we get past the deliberate obfuscation, it is quite an elegant approach. +Though perhaps not the most idiomatic Python. + +## Credit + +We owe this approach to @MAPKarrenbelt, who must have had fun with it. + +[starmap]: https://docs.python.org/3/library/itertools.html#itertools.starmap +[pep8]: https://peps.python.org/pep-0008/#programming-recommendations diff --git a/exercises/practice/roman-numerals/.approaches/itertools-starmap/snippet.txt b/exercises/practice/roman-numerals/.approaches/itertools-starmap/snippet.txt new file mode 100644 index 0000000000..4f5e75e6db --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/itertools-starmap/snippet.txt @@ -0,0 +1,7 @@ +from itertools import starmap + +def roman(number: int) -> str: + orders = [(1000, "M "), (100, "CDM"), (10, "XLC"), (1, "IVX")] + options = lambda I, V, X: ["", I, I * 2, I * 3, I + V, V, V + I, V + I * 2, V + I * 3, I + X] + compute = lambda n, chars: options(*chars)[number % (n * 10) // n] + return "".join(starmap(compute, orders)) diff --git a/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md b/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md new file mode 100644 index 0000000000..6e1d4cc847 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md @@ -0,0 +1,125 @@ +# Loop Over Roman Numerals + +```python +ROMAN = {1000: 'M', 900: 'CM', 500: 'D', 400: 'CD', + 100: 'C', 90: 'XC', 50: 'L', 40: 'XL', + 10: 'X', 9: 'IX', 5: 'V', 4: 'IV', 1: 'I'} + +def roman(number: int) -> str: + result = '' + while number: + for arabic in ROMAN.keys(): + if number >= arabic: + result += ROMAN[arabic] + number -= arabic + break + return result +``` + +This approach is one of a family, using some mapping from Arabic (decimal) to Roman numbers. + +The code above uses a dictionary. +With minor changes, we could also use nested tuples: + +```python +ROMANS = ((1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), + (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), + (9, "IX"), (5, "V"), (4, "IV"), (1, "I")) + +def roman(number: int) -> str: + assert(number > 0) + + roman_num = "" + for (k, v) in ROMANS: + while k <= number: + roman_num += v + number -= k + return roman_num +``` + +Using a pair of lists is also possible, with a shared index from the `enumerate()`. + +```python +# Use a translation +numbers = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] +names = [ 'M', 'CM','D','CD', 'C','XC','L','XL', 'X','IX','V','IV', 'I'] + +def roman(number: int) -> str: + "Take a decimal number and return Roman Numeral Representation" + + # List of Roman symbols + res = [] + + while (number > 0): + # Find the largest amount we can chip off + for i, val in enumerate(numbers): + if (number >= val): + res.append(names[i]) + number -= val + break + + return ''.join(res) +``` + +However, for a read-only lookup it may be better to use (immutable) tuples for `numbers` and `names`. + +As Roman numerals are built up from letters for 1, 5, 10 times powers of 10, it is possible to shorten the lookup and build up most of the digits programmatically: + +```python +# The 10's, 5's and 1's position chars for 1, 10, 100, 1000. +DIGIT_CHARS = ["XVI", "CLX", "MDC", "??M"] + + +def roman(number: int) -> str: + """Return the Roman numeral for a number.""" + # Generate a mapping from numeric value to Roman numeral. + mapping = [] + for position in range(len(DIGIT_CHARS) - 1, -1, -1): + # Values: 1000, 100, 10, 1 + scale = 10 ** position + chars = DIGIT_CHARS[position] + # This might be: (9, IX) or (90, XC) + mapping.append((9 * scale, chars[2] + chars[0])) + # This might be: (5, V) or (50, D) + mapping.append((5 * scale, chars[1])) + # This might be: (4, IV) or (40, XD) + mapping.append((4 * scale, chars[2] + chars[1])) + mapping.append((1 * scale, chars[2])) + + out = "" + for num, numerals in mapping: + while number >= num: + out += numerals + number -= num + return out +``` + +The code below is doing something similar to the dictionary approach at the top of this page, but more concisely: + +```python +def roman(number: int) -> str: + result = '' + divisor_map = {1000: 'M', 900: 'CM', 500: 'D', 400: 'CD', 100: 'C', 90: 'XC', + 50: 'L', 40: 'XL', 10: 'X', 9: 'IX', 5: 'V', 4: 'IV', 1: 'I'} + for divisor, symbol in divisor_map.items(): + major, number = divmod(number, divisor) + result += symbol * major + return result +``` + + +These five solutions all share some common features: +- Some sort of translation lookup. +- Nested loops, a `while`and a `for`, in either order. +- At each step, find the largest number that can be subtracted from the decimal input and appended to the Roman representation. + +When building a string gradually, it is often better to build an intermediate list, then do a `join()` at the end, as in the third example. +This is because strings are immutable, so need to be copied at each step, and the old strings need to be garbage-collected. + +However, Roman numerals are always so short that the difference is minimal in this case. + +Incidentally, notice the use of type hints: `def roman(number: int) -> str`. +This is optional in Python and (currently) ignored by the interpreter, but is useful for documentation purposes. + +Increasingly, IDE's such as VSCode and PyCharm understand the type hints, using them to flag problems and provide advice. + diff --git a/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt b/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt new file mode 100644 index 0000000000..2ce6c141c2 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt @@ -0,0 +1,8 @@ +def roman(number): + assert(number > 0) + roman_num = "" + for (k, v) in ROMANS: + while k <= number: + roman_num += v + number -= k + return roman_num diff --git a/exercises/practice/roman-numerals/.approaches/recurse-match/content.md b/exercises/practice/roman-numerals/.approaches/recurse-match/content.md new file mode 100644 index 0000000000..3e0bb0ca5f --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/recurse-match/content.md @@ -0,0 +1,57 @@ +# Recursion with Pattern Matching + +```python +ARABIC_NUM = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) +ROMAN_NUM = ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") + +def roman(number: int) -> str: + return roman_recur(number, 0, []) + +def roman_recur(num: int, idx: int, digits: list[str]): + match (num, idx, digits): + case [_, 13, digits]: + return ''.join(digits[::-1]) + case [num, idx, digits] if num >= ARABIC_NUM[idx]: + return roman_recur(num - ARABIC_NUM[idx], idx, [ROMAN_NUM[idx],] + digits) + case [num, idx, digits]: + return roman_recur(num, idx + 1, digits) +``` + +[Recursion][recursion] is possible in Python, but it is much less commonly used than in some other languages. + +A limitation is the lack of tail-recursion optimization, which can easily trigger stack overflow if the recursion goes too deep. +The maximum recursion depth for Python defaults to 1000 to avoid this overflow. + +However, Roman numerals are so limited in scale that they could be an ideal use case for playing with recursion. +In practice, there is no obvious advantage to recursion over using a loop (_everything you can do with recursion you can do with a loop and vice-versa_) . + +Note the use of [structural pattern matching][pep-636], available in Python since version 3.10. +There is also an [official tutorial][structural-pattern-matching] for this new feature. + +The code above is adapted from a Scala approach, where it may be more appropriate. + +Once we get past the unfamiliar-in-Python syntax, this code is doing essentially the same as other [`loop-over-romans`][loop-over-romans] approaches. + +Without the pattern matching, a recursive approach might look something like this: + +```python +LOOKUP = [(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"), (50, "L"), + (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")] + +def convert (number, idx, output): + if idx > 12: + return output + val, ltr = LOOKUP[idx] + if number >= val: + return convert(number - val, idx, output + ltr) + return convert(number, idx + 1, output) + +def roman(number): + return convert(number, 0, "") +``` + + +[recursion]: https://diveintopython.org/learn/functions/recursion +[pep-636]: https://peps.python.org/pep-0636/ +[structural-pattern-matching]: https://docs.python.org/3/tutorial/controlflow.html#match-statements +[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman diff --git a/exercises/practice/roman-numerals/.approaches/recurse-match/snippet.txt b/exercises/practice/roman-numerals/.approaches/recurse-match/snippet.txt new file mode 100644 index 0000000000..d5b6d091a3 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/recurse-match/snippet.txt @@ -0,0 +1,8 @@ +def roman_recur(num: int, idx: int, digits: list[str]): + match (num, idx, digits): + case [_, 13, digits]: + return ''.join(digits[::-1]) + case [num, idx, digits] if num >= ARABIC_NUM[idx]: + return roman_recur(num - ARABIC_NUM[idx], idx, [ROMAN_NUM[idx],] + digits) + case [num, idx, digits]: + return roman_recur(num, idx + 1, digits) \ No newline at end of file diff --git a/exercises/practice/roman-numerals/.approaches/table-lookup/content.md b/exercises/practice/roman-numerals/.approaches/table-lookup/content.md new file mode 100644 index 0000000000..e0ea07539f --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/table-lookup/content.md @@ -0,0 +1,68 @@ +# Table Lookup + +```python +def roman(number): + assert (number > 0) + + # define lookup table (as a tuple of tuples, in this case) + table = ( + ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), + ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), + ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), + ("M", "MM", "MMM")) + + # convert the input integer to a list of single digits + digits = [int(d) for d in str(number)] + + # we need the row in the lookup table for our most-significant decimal digit + inverter = len(digits) - 1 + + # translate decimal digits list to Roman numerals list + roman_digits = [table[inverter - i][d - 1] for (i, d) in enumerate(digits) if d != 0] + + # convert the list of Roman numerals to a single string + return ''.join(roman_digits) +``` + +In this approach we loop over decimal digits, not their Roman equivalents. + +The key point is to have a 2-dimensional lookup table, with each row corresponding to a separate digit: ones, tens, hundreds, thousands. +Each digit can then be converted to its Roman equivalent with a single lookup. + +Note that we need to compensate for Python's zero-based indexing by (in effect) subtracting 1 from each row and column. + +## Optional modification + +In the code above, we used the `inverter` variable to work bottom-to-top through the lookup table. +This allows working left-to-right through the decimal digits. + +Alternatively, we could reverse the `digits` list, go top-to-bottom through the lookup table, then reverse the `roman_digits` list before the final `join()`. + +```python +def roman(number): + assert (number > 0) + + # define lookup table (as a tuple of tuples, in this case) + table = ( + ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), + ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), + ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), + ("M", "MM", "MMM")) + + # convert the input integer to a list of single digits, in reverse order + digits = [int(d) for d in str(number)][::-1] + + # translate decimal digits list to Roman numerals list + roman_digits = [table[i][d - 1] for (i, d) in enumerate(digits) if d != 0] + + # reverse the list of Roman numerals and convert to a single string + return ''.join(roman_digits[::-1]) +``` + +This eliminates one line of code, at the cost of adding two list reverses. + +The `[::-1]` indexing is idiomatic Python, but less experienced programmers may not find it very readable. + +## Credit + +This approach was adapted from one created by @cmcaine on the Julia track. diff --git a/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt b/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt new file mode 100644 index 0000000000..9d69b8c5da --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt @@ -0,0 +1,8 @@ + table = ( + ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), + ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), + ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), + ("M", "MM", "MMM")) + digits = [int(d) for d in str(number)][::-1] + roman_digits = [table[i][d - 1] for (i, d) in enumerate(digits) if d != 0] + return ''.join(roman_digits[::-1]) From 24e9d6d5dc06cb65b74ec42d52fe86f06130fee3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 1 Feb 2024 02:38:52 -0800 Subject: [PATCH 598/826] [Raindrops] Approaches and Performance Article (#3362) * First commit of raindrops approaches. * Placeholders and starts to content to make CI happier. * Edited to conform to configlet line limit. * One more line trimmed. * Adding regenerated darts test file to stop CI complaint. * Added approaches and benchmark article for Raindrops. * Updated branch with current track config.json from main. * Fixed config titles and directories. * Shortened performance snippet to 8 lines. * Added colin to the performance article. --- .../raindrops/.approaches/config.json | 56 ++++ .../.approaches/dict-and-join/content.md | 53 ++++ .../.approaches/dict-and-join/snippet.txt | 8 + .../.approaches/functools-reduce/content.md | 40 +++ .../.approaches/functools-reduce/snippet.txt | 8 + .../.approaches/if-statements/content.md | 47 +++ .../.approaches/if-statements/snippet.txt | 8 + .../raindrops/.approaches/introduction.md | 285 ++++++++++++++++++ .../.approaches/itertools-compress/content.md | 48 +++ .../itertools-compress/snippet.txt | 6 + .../.approaches/loop-and-fstring/content.md | 44 +++ .../.approaches/loop-and-fstring/snippet.txt | 8 + .../.approaches/sequence-with-join/content.md | 37 +++ .../sequence-with-join/snippet.txt | 8 + .../truthy-and-falsey-with-fstring/content.md | 39 +++ .../snippet.txt | 6 + .../practice/raindrops/.articles/config.json | 11 + .../.articles/performance/code/Benchmark.py | 167 ++++++++++ .../.articles/performance/content.md | 60 ++++ .../.articles/performance/snippet.md | 8 + 20 files changed, 947 insertions(+) create mode 100644 exercises/practice/raindrops/.approaches/config.json create mode 100644 exercises/practice/raindrops/.approaches/dict-and-join/content.md create mode 100644 exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/functools-reduce/content.md create mode 100644 exercises/practice/raindrops/.approaches/functools-reduce/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/if-statements/content.md create mode 100644 exercises/practice/raindrops/.approaches/if-statements/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/introduction.md create mode 100644 exercises/practice/raindrops/.approaches/itertools-compress/content.md create mode 100644 exercises/practice/raindrops/.approaches/itertools-compress/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/loop-and-fstring/content.md create mode 100644 exercises/practice/raindrops/.approaches/loop-and-fstring/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/sequence-with-join/content.md create mode 100644 exercises/practice/raindrops/.approaches/sequence-with-join/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/content.md create mode 100644 exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/snippet.txt create mode 100644 exercises/practice/raindrops/.articles/config.json create mode 100644 exercises/practice/raindrops/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/raindrops/.articles/performance/content.md create mode 100644 exercises/practice/raindrops/.articles/performance/snippet.md diff --git a/exercises/practice/raindrops/.approaches/config.json b/exercises/practice/raindrops/.approaches/config.json new file mode 100644 index 0000000000..ec50a5f52d --- /dev/null +++ b/exercises/practice/raindrops/.approaches/config.json @@ -0,0 +1,56 @@ +{ + "introduction": { + "authors": ["BethanyG"] + }, + "approaches": [ + { + "uuid": "afc3b20c-c84b-45b0-97e5-5c927397274c", + "slug": "if-statements", + "title": "if statements", + "blurb": "Use a series of if statements and string concatenation.", + "authors": ["BethanyG"] + }, + { + "uuid": "55b0a949-e84e-4772-8a2c-368b491d72e2", + "slug": "loop-and-fstring", + "title": "Loop and f-string", + "blurb": "Loop through a tuple and assemble output via an f-string.", + "authors": ["BethanyG"] + }, + { + "uuid": "9785ad3a-fc52-409d-bfa4-d97926a87d22", + "slug": "sequence-with-join", + "title": "Sequence(s) with str.join()", + "blurb": "Use one or more sequences and str.join() with a generator-expression.", + "authors": ["BethanyG"] + }, + { + "uuid": "b4c3608e-d4f4-4a50-8da7-66c655241950", + "slug": "truthy-and-falsey-with-fstring", + "title": "Truthy and Falsey Values with f-strings", + "blurb": "Use ternaries to build words and an f-string to assemble the result.", + "authors": ["BethanyG"] + }, + { + "uuid": "78392dda-921b-4308-bdc7-8993898987ba", + "slug": "dict-and-join", + "title": "Dict and str.join()", + "blurb": "Use a dict to hold factors:values and str.join() with a generator-expression.", + "authors": ["BethanyG"] + }, + { + "uuid": "7e485b55-6f47-4288-ac18-d9f57fbdc0c2", + "slug": "itertools-compress", + "title": "itertools.compress", + "blurb": "Use itertools.compress() with a list mask.", + "authors": ["BethanyG"] + }, + { + "uuid": "298fbc79-7946-4cb1-b820-e2e282b1f155", + "slug": "functools-reduce", + "title": "functools.reduce", + "blurb": "Use functools.reduce() and zip().", + "authors": ["BethanyG"] + } + ] +} diff --git a/exercises/practice/raindrops/.approaches/dict-and-join/content.md b/exercises/practice/raindrops/.approaches/dict-and-join/content.md new file mode 100644 index 0000000000..ae302ada8d --- /dev/null +++ b/exercises/practice/raindrops/.approaches/dict-and-join/content.md @@ -0,0 +1,53 @@ +# Dict and str.join() + + +```python +def convert(number): + sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} + + results = ''.join(sounds[divisor] for + divisor, sound in sounds.items() + if number % divisor == 0) + + return results or str(number) +``` + +This approach uses a [dictionary][dict] called 'sounds' with factors as `keys` and sound strings as `values`. +A [generator-expression][generator-expressions] inside of [`str.join()`][str.join] loops through the [dictionary view object][dict-view-object] [`sounds.items()`][dict.items()], which is a series of (key, value) `tuples`. + Each `value` is looked up for every factor where number % divisor == 0. + `str.join()` then compiles the results. + +This is the equivalent of: + +```python +def convert(number): + sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} + results = [] + + for divisor in sounds.keys(): + if number % divisor == 0: + # Looks up the value by the divisor key and appends to the results list. + results.append(sounds[divisor]) + + return ''.join(results) or str(number) +``` + +The advantage of the generator expression is that no intermediary `list` is created in memory. +This will definitely save memory, and might also be slightly faster than a "classic" loop that appends to a `list`. + +Finally, this could all be done as a 'one liner'. +But this becomes both harder to read and harder to maintain: + +```python +def convert(number): + return ''.join(sound for divisor, sound in + {3: 'Pling', 5: 'Plang', 7: 'Plong'}.items() + if (number % divisor == 0)) or str(number) +``` + +[dict-view-object]: https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects +[dict.items()]: https://docs.python.org/3/library/stdtypes.html#dict.items +[dict]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[generator-expressions]: https://www.pythonmorsels.com/how-write-generator-expression/ +[str.join]: https://docs.python.org/3/library/stdtypes.html#str.join + diff --git a/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt b/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt new file mode 100644 index 0000000000..6c24abb8df --- /dev/null +++ b/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt @@ -0,0 +1,8 @@ +def convert(number): + sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} + + results = ''.join(sounds[divisor] for + divisor, sound in sounds.items() + if number % divisor == 0) + + return results or str(number) diff --git a/exercises/practice/raindrops/.approaches/functools-reduce/content.md b/exercises/practice/raindrops/.approaches/functools-reduce/content.md new file mode 100644 index 0000000000..36dd2222fd --- /dev/null +++ b/exercises/practice/raindrops/.approaches/functools-reduce/content.md @@ -0,0 +1,40 @@ +# Use functools.reduce() + + +```python +from functools import reduce + +def convert(number): + sounds = ('Pling','Plang','Plong') + factors = ((number % factor) == 0 for factor in (3,5,7)) #1 if no remainder, 0 if a remainder. + result = reduce(lambda sound, item : sound + (item[0] * item[1]), zip(sounds, factors), '') + + return result or str(number) +``` + +This is essentially the same strategy as the [itertools.compress()][approach-itertools-compress] approach, but uses [`functools.reduce`][functools-reduce] with a [`lambda` expression][lambda] and string multiplication as a kind of mask. + +The factors are calculated and then [`zip()`ed][zip] with the sounds into `tuples`. +Each string (_position 0 in the `tuple`_) is multiplied by the 'factor' (_a 1 if there is no remainder and a 0 if there is a remainder_). +The result is then 'reduced' to a combined result string with an empty string as an initializer. +If the result is empty, the number as a string is returned instead. + +This is an interesting use of `functools.reduce()`, but not necessarily the most readable way to solve this exercise. +Importing `functools` adds overhead and separating the sounds from the factors risks errors as the factor list expands. +The syntax of `reduce()` also obfuscates string creation/concatenation. +Unless the team maintaining this code is comfortable with `functools.reduce()`, this might be better re-written using `join()`: + + +```python +def convert(number): + sounds = ('Pling','Plang','Plong') + factors = ((number % factor) == 0 for factor in (3,5,7)) + result = ''.join((item[0] * item[1]) for item in zip(sounds, factors)) + + return result or str(number) +``` + +[lambda]: https://dbader.org/blog/python-lambda-functions +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[approach-itertools-compress]: https://exercism.org/tracks/python/exercises/raindrops/approaches/itertools-compress +[zip]: https://docs.python.org/3/library/functions.html#zip diff --git a/exercises/practice/raindrops/.approaches/functools-reduce/snippet.txt b/exercises/practice/raindrops/.approaches/functools-reduce/snippet.txt new file mode 100644 index 0000000000..d3b4865013 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/functools-reduce/snippet.txt @@ -0,0 +1,8 @@ +from functools import reduce + +def convert(number): + sounds = ('Pling','Plang','Plong') + factors = ((number % factor) == 0 for factor in (3,5,7)) + result = reduce(lambda sound, item : sound + (item[0] * item[1]), zip(sounds, factors), '') + + return result or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/if-statements/content.md b/exercises/practice/raindrops/.approaches/if-statements/content.md new file mode 100644 index 0000000000..80fe84bd92 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/if-statements/content.md @@ -0,0 +1,47 @@ +# `if` Statements + +```python +def convert(num): + sounds = '' + + if num % 3 == 0: sounds += 'Pling' + if num % 5 == 0: sounds += 'Plang' + if num % 7 == 0: sounds += 'Plong' + + return sounds or str(num) +``` + + +This approach is the most straightforward or 'naive' - it replicates in code what the instructions say, using `if` statements to check the modulo for each factor. +If the number is evenly divisible by the factor (modulo == 0), the corresponding string is concatenated to _sounds_ via the `+` operator. +Sounds is returned if it is not empty (_see [Truth Value Testing][truth-value-testing] for more info_). +Otherwise, a `str` version of the input number is returned. + +This, of course incurs the 'penalty' of string concatenation. +But since there are only three factors to check and the strings are small, the concatenation is at a minimum. + +In fact, this solution - and most others described in the approaches here - are `O(1)` time complexity. +There are a constant number of factors to iterate through, and the work that is done never increases, even as the input numbers get bigger. +This holds true for space complexity as well. + +The compact form for the `if` statements might be harder to read for some people. +These can be re-written to be nested, and the return can be re-written to use a ternary expression: + +```python +def convert(num): + sounds = '' + + if num % 3 == 0: + sounds += 'Pling' + if num % 5 == 0: + sounds += 'Plang' + if num % 7 == 0: + sounds += 'Plong' + + return sounds if sounds else str(num) +``` + +While this solution is nicely readable and to-the-point, it will grow in length and get harder to read if many more factors are added or business logic changes. +Other solutions using data structures to hold factors might be a better option in 'high change' situations. + +[truth-value-testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/practice/raindrops/.approaches/if-statements/snippet.txt b/exercises/practice/raindrops/.approaches/if-statements/snippet.txt new file mode 100644 index 0000000000..2cbc9a5d96 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/if-statements/snippet.txt @@ -0,0 +1,8 @@ +def convert(num): + sounds = '' + + if num % 3 == 0: sounds += 'Pling' + if num % 5 == 0: sounds += 'Plang' + if num % 7 == 0: sounds += 'Plong' + + return sounds or str(num) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/introduction.md b/exercises/practice/raindrops/.approaches/introduction.md new file mode 100644 index 0000000000..1783fa33ab --- /dev/null +++ b/exercises/practice/raindrops/.approaches/introduction.md @@ -0,0 +1,285 @@ +# Introduction + + +The goal of the Raindrops exercise is to convert a number to a string of "raindrop sounds", based on whether the number +is divisible by any combination of 3, 5, or 7. +Numbers not divisible by any of these factors are returned as a string. + +There are many different ways to solve this exercise in Python, with many many variations. +Some strategies optimize for simplicity, others for efficiency or extendability. +Others explore interesting features of Python. + +Here, we go over 7 approaches: + + +1. Using `if` statements and `+` to assemble a return string. +2. Exploiting "Truthy" and "Falsy" values in ternary checks and an `f-string` +3. Using one or more sequences, a `loop`, and an `f-string`. +4. Using one or more sequences and a `generator-expression` within `join()`. +5. Using a `dict` of `{factors : sounds}` for lookup and a `generator-expression` within `join()`. +6. Using `itertools.compress()` and a `generator expression` within `join()`. +7. Using `functools.reduce()` and `join()`. + + +## General Guidance + + +The goal of the Raindrops exercise is to return a string that represents which factors (`3`, `5` or `7`) evenly divide the input number. +Each factor has a _sound_ assigned ('Pling' (3), 'Plang' (5), or 'Plong'(7)). + + +Determining which factors divide into the input number without a remainder can most easily be accomplished via the [modulo][modulo] `%` operator. +However, it is also possible to import the [`math`][math-module] module and use the [`math.fmod()`][fmod] function for similar results, or the general built-in [`divmod()`][divmod] . +Keep in mind that `math.fmod()` returns a _float_ and `divmod()` returns a `tuple` of (quotient, remainder) that must be unpacked before use. +Using [`math.remainder()`][remainder] is **not** recommended for this exercise. + + +The challenge is to efficiently assemble a results string while also keeping in mind how the code might be maintained or extended. +How might you add additional factors without a complete rewrite of code? +How can you keep the code concise? +Do you use string concatenation, or are there other options you can choose? +What tradeoffs will you make between readability, space, and speed of processing? + + +## Approach: Using `if` statements + +```python +def convert(num): + sounds = '' + + if num % 3 == 0: sounds += 'Pling' + if num % 5 == 0: sounds += 'Plang' + if num % 7 == 0: sounds += 'Plong' + + return sounds if sounds else str(num) +``` + +This approach is the most straightforward or 'naive' - it replicates in code what the instructions say, using `if` statements to check the modulus for each factor. +Each is then concatenated to the 'sounds' string. +If the 'sounds' string is empty, a string version of the number is returned instead. + +For more details, see the [if statement][approach-if-statements] approach. + + +## Approach: Using "Truthy" and "Falsy" Values with `f-string`s + +```python +def convert(number): + threes = '' if number % 3 else 'Pling' # Empty string if there is a remainder + fives = '' if number % 5 else 'Plang' + sevens = '' if number % 7 else 'Plong' + + return f'{threes}{fives}{sevens}' or str(number) + + ###OR### + +def convert(number): + threes = 'Pling' if not number % 3 else '' # Sound if there is NOT a remainder + fives = 'Plang' if not number % 5 else '' + sevens = 'Plong' if not number % 7 else '' + + return f'{threes}{fives}{sevens}' or str(number) +``` + +This approach uses [ternary expressions][ternary expression] (_also known as 'conditional expressions'_) to build strings for each factor. +The result strings are combined via an `f-string`, avoiding the use of `join()`, or a `loop`. +If the `f-string` is empty _(evaluating to False in a [boolean context][truth-value-testing]_), a `str` of the input number is returned instead. + +For more information, see the [Truthy and Falsy with f-string][approach-truthy-and-falsey-with-fstring] approach. + + +## Approach: Using a `loop`, and an `f-string` + + +```python +def convert(number): + sounds = '' + drops = ("i", 3), ("a", 5), ("o", 7) + + for vowel, factor in drops: + if number % factor == 0: + sounds += f'Pl{vowel}ng' + + return sounds or str(number) +``` + +This approach loops through the _drops_ `tuple` (_although any iterable sequence can be used_), unpacking each `vowel` and `factor`. +If the input number is evenly divisible by the factor (_modulus == 0_), the corresponding vowel is inserted into the `f-string` for that factor. +The `f-string` is then concatenated to _sounds_ string via `+`. + _Sounds_ is returned if it is not empty. + Otherwise, a string version of the input number is returned. + +For more details, see the [loop and f-string][approach-loop-and-fstring] approach. + + +## Approach: Using Sequence(s) with `join()` + +```python +def convert(number): + drops = ["Pling","Plang","Plong"] + factors = [3,5,7] + sounds = ''.join(drops[index] for + index, factor in + enumerate(factors) if (number % factor == 0)) + + return sounds or str(number) +``` + + +This approach is very similar to the [loop and f-string][approach-loop-and-fstring] approach, but uses two `lists` to hold factors and sounds. + It also converts the loop that calculates the remainders and sounds into a [`generator expression`][generator-expression] within `join()`, which assembles the 'sounds' string. +_Sounds_ is returned if it is not empty. + Otherwise, a `str` version of the input number is returned. + +For more information, see the [tuple with join][approach-sequence-with-join] approach. + + +## Approach: Using a `dict` and `join()` + +```python +def convert(number): + + sounds = {3: 'Pling', + 5: 'Plang', + 7: 'Plong'} + + results = ''.join(sounds[divisor] for + divisor, sound in sounds.items() + if number % divisor == 0) + + return results or str(number) +``` + +This approach uses a dictionary to hold the factor -> sound mappings and a `generator-expression` within `join()` to assemble results. +If 'results' is empty, a string version of the input number is returned. + +For more details, read the [`dict` and `join()`][approach-dict-and-join] approach. + + +## Approach: Using `itertools.compress` and a `list` Mask + +```python +from itertools import compress + +def convert(number): + sounds =('Pling','Plang','Plong') + mask = ((number % factor) == 0 for factor in (3,5,7)) + return ''.join(compress(sounds, mask)) or str(number) + +``` + + +This approach uses [`itertools.compress`][itertools-compress] to filter a list of sounds using a mask of `True` and `False` values. +The mask is formed by calculating `bool((input % factor) == 0)` for each factor (_which will return True or False_). +If the result of `itertools.compress` is empty, a string version of the input number is returned instead. + +For more details, see the [itertools.compress][approach-itertools-compress] approach. + + +## Approach: Using `functools.reduce())` and `zip()` + + +```python +from functools import reduce + +def convert(number): + sounds = ('Pling','Plang','Plong') + factors = ((number % factor) == 0 for factor in (3,5,7)) + result = reduce(lambda sound, item : sound + (item[0] * item[1]), zip(sounds, factors), '') + + return result or str(number) +``` + +This approach uses `functools.reduce` to join _sounds_ together using the `int` value of `True` (1) and `False` (0). +Sounds are combined with their corresponding factor values in pairs via `zip()`, and subsequently unpacked for use in the [`functools.reduce`][functools-reduce] [`lambda` expression][lambda]. +It is very similar to the `itertools.compress` method, but uses multiplication rather than mask to add or omit a given string value. + +For more information, read the [functools.reduce][approach-functools-reduce] approach. + + +## Other approaches + +Besides these seven approaches, there are a multitude of possible variations using different data structures and joining methods. + +One can also use the new [structural pattern matching][structural-pattern-matching], although it is both more verbose and harder to read. +It (unnecessarily) lists out all of the mask variations from the [itertools compress][itertools-compress] approach and would be hard to extend without the potential of making a mistake with the factors, the sounds, or the masks: + + +```python +def convert(number): + + match [(number % factor) == 0 for factor in (3,5,7)]: + case [True, True, True]: + return 'PlingPlangPlong' + case [True, True, False]: + return 'PlingPlang' + case [False, True, True]: + return 'PlangPlong' + case [True, False, True]: + return 'PlingPlong' + case [True, False, False]: + return 'Pling' + case [False, False, True]: + return 'Plong' + case [False, True, False]: + return 'Plang' + case _: + return str(number) +``` + + +## Which Approach to Use? + +All approaches are idiomatic, and show multiple paradigms and possibilities. + +Some additional considerations include readability and maintainability. +Approaches using separate data structures to hold sounds/factors are very helpful when additional data needs to be added, even if there is memory or performance overhead associated with them. +No one wants to add to an ever-growing block of `if-statements`, or reap the consequences of troubleshooting a typo in some strange embedded set of parenthesis. +Approaches using `join()` or `loops` are fairly succinct, and might be more easily understood by others reading your code, so that they can adjust or add to the logic. +Additionally, using an `f-string` can cut down on visual "noise" as you assemble the return string. + +So an approach that balances maintenance needs with succinctness is likely the best option: + +```python +def convert(number): + #This is clear and easily added to. Unless the factors get + # really long, this won't take up too much memory. + sounds = {3: 'Pling', + 5: 'Plang', + 7: 'Plong'} + + results = (sounds[divisor] for + divisor in sounds.keys() + if number % divisor == 0) + + return ''.join(results) or str(number) +``` + + +This separates the code that calculates the results string from the factors themselves. +If a factor needs to be added, only the dictionary needs to be touched. +This code need only iterate over the keys of the dictionary to do its calculation, making this `O(1)` in time complexity. +This does take `O(m)` space, where `m` is equal to the number of factor entries. +Since the number of factors is fixed here, this is unlikely to create issues unless a great many more are added to the 'sounds' `dict`. + +To compare the performance of this and the other approaches, take a look at the [Performance article][article-performance]. + +[approach-dict-and-join ]: https://exercism.org/tracks/python/exercises/raindrops/approaches/dict-and-join +[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/raindrops/approaches/functools-reduce +[approach-if-statements]: https://exercism.org/tracks/python/exercises/raindrops/approaches/if-statements +[approach-itertools-compress]: https://exercism.org/tracks/python/exercises/raindrops/approaches/itertools-compress +[approach-loop-and-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/loop-and-fstring +[approach-sequence-with-join]: https://exercism.org/tracks/python/exercises/raindrops/approaches/sequence-with-join +[approach-truthy-and-falsey-with-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/truthy-and-falsey-with-fstring +[article-performance]: https://exercism.org/tracks/python/exercises/raindrops/articles/performance +[divmod]: https://docs.python.org/3/library/functions.html#divmod +[fmod]: https://docs.python.org/3/library/math.html#math.fmod +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[generator-expression]: https://peps.python.org/pep-0289/ +[itertools-compress]: https://docs.python.org/3/library/itertools.html#itertools.compress +[lambda]: https://dbader.org/blog/python-lambda-functions +[math-module]: https://docs.python.org/3/library/math.html +[modulo]: https://www.freecodecamp.org/news/the-python-modulo-operator-what-does-the-symbol-mean-in-python-solved/ +[remainder]: https://docs.python.org/3/library/math.html#math.remainder +[ternary expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions +[truth-value-testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/practice/raindrops/.approaches/itertools-compress/content.md b/exercises/practice/raindrops/.approaches/itertools-compress/content.md new file mode 100644 index 0000000000..b5da476862 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/itertools-compress/content.md @@ -0,0 +1,48 @@ +# Use itertools.compress() + + +```python +from itertools import compress + +def convert(number): + sounds ='Pling','Plang','Plong' + mask = ((number % factor) == 0 for factor in (3,5,7)) + return ''.join(compress(sounds, mask)) or str(number) +``` + +This approach uses both a `tuple` for storing sound strings and mask to filter them with. +For each factor, the `generator-expression` calculates `True` (_evenly divisible_) or `False` (_remainder left over_). +This mask is used with [`itertools.compress()`][compress] to 'mask over' any unwanted values in 'sounds'. +Finally, the returned string is created with [`str.join()`][join]. +If the 'sounds' string is empty, a string version of the number is returned instead. + +This is very succinct code that avoids string concatenation. +However, it does require the overhead of importing `compress()` from the [itertools][itertools] module. +The code is also harder to maintain should there be additional factors/sounds needed. +Because the factors and sounds are seperated, there is a chance mistakes could be made like forgetting a number or swapping which factor is paired with which sound. + +A better approach for maintenance might be to turn the 'sounds' `tuple` into a dictionary where the factors and sounds can be stored separate from the logic that does the calculations and string creation: + +```python +from itertools import compress + +def convert(number): + sounds = {3 : 'Pling', + 5 : 'Plang', + 7 : 'Plong' + } + mask = ((number % factor) == 0 for factor in sounds.keys()) + return ''.join(compress(sounds.values(), mask)) or str(number) +``` + + +In this rewrite, factors are keys with the sounds as values in a dictionary. +The mask then uses a [`dict.keys()`][dict-keys] [view][view objects] to iterate over the factors and calculate the `True`/`False` values. +This mask is used with `compress()` to filter a `dict.values()` view that is used by `join()` to construct the return string. +If the string is empty, a string version of the input number is returned instead. + +[compress]: https://docs.python.org/3/library/itertools.html#itertools.compress +[dict-keys]: https://docs.python.org/3/library/stdtypes.html#dict.keys +[itertools]: https://docs.python.org/3/library/itertools.html +[join]: https://docs.python.org/3/library/stdtypes.html#str.join +[view objects]: https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects diff --git a/exercises/practice/raindrops/.approaches/itertools-compress/snippet.txt b/exercises/practice/raindrops/.approaches/itertools-compress/snippet.txt new file mode 100644 index 0000000000..5a58c913f9 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/itertools-compress/snippet.txt @@ -0,0 +1,6 @@ +from itertools import compress + +def convert(number): + sounds ='Pling','Plang','Plong' + mask = ((number % factor) == 0 for factor in (3,5,7)) + return ''.join(compress(sounds, mask)) or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/loop-and-fstring/content.md b/exercises/practice/raindrops/.approaches/loop-and-fstring/content.md new file mode 100644 index 0000000000..41d4ce4951 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/loop-and-fstring/content.md @@ -0,0 +1,44 @@ +# Sequence(s) with a Loop and f-string + + +```python +def convert(number): + sounds = '' + drops = ("i", 3), ("a", 5), ("o", 7) + + for vowel, factor in drops: + if number % factor == 0: + sounds += f'Pl{vowel}ng' + + return sounds or str(number) +``` + + +This approach loops through the _drops_ `tuple` (_although any iterable sequence(s) can be used_), unpacking each `vowel` and `factor`. +If the input number is evenly divisible by the factor (_modulus == 0_), the corresponding vowel is inserted into the `f-string` for that factor. +The `f-string` is then concatenated to _sounds_ string via `+`. + _Sounds_ is returned if it is not empty. + Otherwise, a string version of the input number is returned. + + This takes `O(1)` time and `O(1)` space. + +It is a very efficient and clever way of building up the return string, since only one vowel is changing per 'drop'. +However, it might take a moment for others reading the code to understand what exactly is going on. +It also (may) create maintenance difficulties should there be future factors and sounds that do not conform to the pattern of only changing the vowel in the sound. + +A much less exciting (_but perhaps easier to maintain_) rewrite would be to store the whole drop sound and build up the return string out of whole drops: + + +```python +def convert(number): + sounds = (3, 'Pling'), (5, 'Plang'), (7, 'Plong') + output = '' + + for factor, sound in sounds: + if number % factor == 0: + output += sound + + return output or str(number) +``` + +This has the same time and space complexity as the first variation. diff --git a/exercises/practice/raindrops/.approaches/loop-and-fstring/snippet.txt b/exercises/practice/raindrops/.approaches/loop-and-fstring/snippet.txt new file mode 100644 index 0000000000..a97a7f70b5 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/loop-and-fstring/snippet.txt @@ -0,0 +1,8 @@ +def convert(number): + sounds = '' + drops = ("i", 3), ("a", 5), ("o", 7) + + for vowel, factor in drops: + if number % factor == 0: + sounds += f'Pl{vowel}ng' + return sounds or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/sequence-with-join/content.md b/exercises/practice/raindrops/.approaches/sequence-with-join/content.md new file mode 100644 index 0000000000..6895052a61 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/sequence-with-join/content.md @@ -0,0 +1,37 @@ +# Sequence(s) with str.join() + +```python +def convert(number): + drops = ["Pling","Plang","Plong"] + factors = [3,5,7] + sounds = ''.join(drops[index] for + index, factor in + enumerate(factors) if (number % factor == 0)) + + return sounds or str(number) +``` + +This approach is very similar to the [loop and f-string][approach-loop-and-fstring] approach, but uses two `lists` to hold factors and sounds and [`enumerate()`][enumerate] to extract an index. + It also converts the loop that calculates the remainders and sounds into a [`generator expression`][generator-expression] within `join()`. +_Sounds_ is returned if it is not empty. + Otherwise, a `str` version of the input number is returned. + + This, like most approaches described here is `O(1)` time and space complexity. + In benchmark timings, the `generator-expression` and multiple variables add a bit of overhead. + + Readability here might not be the easiest for those not used to reading generator expressions inside calls to other functions, so if that is the case, this can be re-written as: + + + ```python +def convert(number): + drops = ["Pling","Plang","Plong"] + factors = [3,5,7] + sounds = (drops[index] for index, factor in + enumerate(factors) if (number % factor == 0)) + + return ''.join(sounds) or str(number) +``` + +[approach-loop-and-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/loop-and-fstring +[generator-expression]: https://peps.python.org/pep-0289/ +[enumerate]: https://docs.python.org/3/library/functions.html#enumerate diff --git a/exercises/practice/raindrops/.approaches/sequence-with-join/snippet.txt b/exercises/practice/raindrops/.approaches/sequence-with-join/snippet.txt new file mode 100644 index 0000000000..2df433229e --- /dev/null +++ b/exercises/practice/raindrops/.approaches/sequence-with-join/snippet.txt @@ -0,0 +1,8 @@ +def convert(number): + drops = ["Pling","Plang","Plong"] + factors = [3,5,7] + sounds = ''.join(drops[index] for + index, factor in + enumerate(factors) if (number % factor == 0)) + + return sounds or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/content.md b/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/content.md new file mode 100644 index 0000000000..816052c3b1 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/content.md @@ -0,0 +1,39 @@ +# Truthy and Falsey Values with f-strings + + +```python +def convert(number): + threes = '' if number % 3 else 'Pling' # Empty string if modulo == 1 (0 evaluates to False) + fives = '' if number % 5 else 'Plang' + sevens = '' if number % 7 else 'Plong' + + return f'{threes}{fives}{sevens}' or str(number) + + #OR# + +def convert(number): + threes = 'Pling' if not number % 3 else '' # Sound if NOT modulo == 0 + fives = 'Plang' if not number % 5 else '' + sevens = 'Plong' if not number % 7 else '' + + return f'{threes}{fives}{sevens}' or str(number) +``` + +This is very similar to the [`if-statement`][approach-if-statements] approach logic, but uses [ternary expressions][ternary expression] to assign either an empty string or a drop sound to a variable. +The variables are then used in an `f-string` to compose the result, avoiding the use of `join()`, or a `loop`. +If the `f-string` is empty _(evaluating to False in a [boolean context][truth-value-testing]_), a `str` of the input number is returned instead. + +This has `O(1)` time and space complexity. + +These two variations both exploit the fact that boolean `True` and `False` are a subtype of `int` in Python. +0 evaluates to `False`, and 1 to `True`. +So the expression `'Pling' if not number % 3 else ''` can be read as "return 'Pling" if number % 3 not False" where `False` is 0, and (not `False`) is 1. +The expression `'' if number % 3 else 'Pling'` is the inverse: "return '' if number % 3 is True" - where number % 3 > 0 is `True`, and number % 3 == 0 is `False`. + +Like the `if-statement` approach, these solutions are nicely readable and to-the-point, but will grow in length and get harder to read if many more factors are added or business logic changes. +The `f-string` in particular could get unwieldy beyond about 5 factors. +Other solutions using data structures to hold factors and `join()` to assemble strings might be a better option in 'high change' situations. + +[approach-if-statements]: https://exercism.org/tracks/python/exercises/raindrops/approaches/if-statements +[ternary expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions +[truth-value-testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/snippet.txt b/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/snippet.txt new file mode 100644 index 0000000000..83a16958e5 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/snippet.txt @@ -0,0 +1,6 @@ +def convert(number): + threes = '' if number % 3 else 'Pling' + fives = '' if number % 5 else 'Plang' + sevens = '' if number % 7 else 'Plong' + + return f'{threes}{fives}{sevens}' or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.articles/config.json b/exercises/practice/raindrops/.articles/config.json new file mode 100644 index 0000000000..c85ae72079 --- /dev/null +++ b/exercises/practice/raindrops/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "79248d29-772d-4618-bd7d-49a9bba693fd", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach to the Raindrops exercise.", + "authors": ["BethanyG", "colinleach"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/raindrops/.articles/performance/code/Benchmark.py b/exercises/practice/raindrops/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..a01978a7ef --- /dev/null +++ b/exercises/practice/raindrops/.articles/performance/code/Benchmark.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Script for timing Raindrops Solutions. + +Creates timing table and timing graphs for +multiple approaches to the Randrops problem in Python. + +Created Jan 2024 +@authors: bethanygarcia, @colinleach +""" + +import timeit + +import pandas as pd +import numpy as np + + +# ------------ FUNCTIONS TO TIME ------------- # + +def convert_if_statements(num): + sounds = '' + + if num % 3 == 0: sounds += 'Pling' + if num % 5 == 0: sounds += 'Plang' + if num % 7 == 0: sounds += 'Plong' + + return sounds if sounds else str(num) + + +def convert_truthy_falsy(number): + threes = '' if number % 3 else 'Pling' # Empty string if there is a remainder + fives = '' if number % 5 else 'Plang' + sevens = '' if number % 7 else 'Plong' + + return f'{threes}{fives}{sevens}' or str(number) + + +def convert_loop(number): + sounds = '' + drops = ("i", 3), ("a", 5), ("o", 7) + + for vowel, factor in drops: + if number % factor == 0: + sounds += f'Pl{vowel}ng' + + return sounds or str(number) + + +def convert_sequence_join(number): + drops = ["Pling", "Plang", "Plong"] + factors = [3, 5, 7] + sounds = ''.join(drops[index] for + index, factor in + enumerate(factors) if (number % factor == 0)) + + return sounds or str(number) + + +def convert_dict(number): + + sounds = {3: 'Pling', + 5: 'Plang', + 7: 'Plong'} + + results = ''.join(sounds[divisor] for + divisor, sound in sounds.items() + if number % divisor == 0) + + return results or str(number) + + +from itertools import compress + +def convert_itertools(number): + sounds = ('Pling', 'Plang', 'Plong') + mask = ((number % factor) == 0 for factor in (3, 5, 7)) + return ''.join(compress(sounds, mask)) or str(number) + + +from functools import reduce + +def convert_functools(number): + sounds = ('Pling', 'Plang', 'Plong') + factors = ((number % factor) == 0 for factor in (3, 5, 7)) + result = reduce(lambda sound, item: sound + (item[0] * item[1]), zip(sounds, factors), '') + + return result or str(number) + + +def convert_pattern_matching(number): + + match [(number % factor) == 0 for factor in (3, 5, 7)]: + case [True, True, True]: + return 'PlingPlangPlong' + case [True, True, False]: + return 'PlingPlang' + case [False, True, True]: + return 'PlangPlong' + case [True, False, True]: + return 'PlingPlong' + case [True, False, False]: + return 'Pling' + case [False, False, True]: + return 'Plong' + case [False, True, False]: + return 'Plang' + case _: + return str(number) + + +## ---------END FUNCTIONS TO BE TIMED-------------------- ## + +## -------- Timing Code Starts Here ---------------------## + + +# Input Data Setup +inputs = [1,5,7,6,8,9,10,14,15,21,27, 35, 49, 52, 70, 105, 144, 182, + 189, 195, 198, 203, 204, 210, 228, 231, 252, 315, 318, 329, + 340, 349, 379, 399, 409, 415, 497, 500, 525, 625, 735, 813, + 1575, 3125, 3250] + + +# #Set up columns and rows for Pandas Data Frame +col_headers = [f'Number: {number}'for number in inputs] +row_headers = ["if statements", + "ternary with truthy/falsy", + "loop with tuple", + "sequence with join", + "dictionary with join", + "itertools with join", + "functools reduce", + "structural pattern matching"] + +# # empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# #Function List to Call When Timing +functions = [convert_if_statements, + convert_truthy_falsy, + convert_loop, + convert_sequence_join, + convert_dict, + convert_itertools, + convert_functools, + convert_pattern_matching] + +# Run timings using timeit.autorange(). Run Each Set 3 Times. +for function, title in zip(functions, row_headers): + timings = [[ + timeit.Timer(lambda: function(data), globals=globals()).autorange()[1] / + timeit.Timer(lambda: function(data), globals=globals()).autorange()[0] + for data in inputs] for rounds in range(3)] + + # Only the fastest Cycle counts. + timing_result = min(timings) + + # timing_result = [round(min(timeit.repeat(lambda: function(data), repeat=3, number=1000000, globals=globals())), 6) for data in words_II] + print(f'{title}', f'Timings : {timing_result}') + + # Insert results into the dataframe + df.loc[title, 'Number: 1':'Number: 3250'] = timing_result + +# The next bit is useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".2e")) + diff --git a/exercises/practice/raindrops/.articles/performance/content.md b/exercises/practice/raindrops/.articles/performance/content.md new file mode 100644 index 0000000000..522ff1016e --- /dev/null +++ b/exercises/practice/raindrops/.articles/performance/content.md @@ -0,0 +1,60 @@ +# Performance + +In this article, we will find out how to most efficiently complete the Raindrops exercise. + +The [introduction page][approaches-intro] lists seven idiomatic approaches to this exercise: + +1. Using [`if` statements and `+`][approach-if-statements] to assemble a return string +2. Exploiting ["Truthy" and "Falsy" values in ternary checks][approach-truthy-and-falsey-with-fstring] and an `f-string` +3. Using one or more sequences, a [`loop`, and an `f-string`][approach-loop-and-fstring] +4. Using one or more [sequences and a `generator-expression` within `join()`][approach-sequence-with-join]. +5. Using a [`dict` of `{factors : sounds}` for lookup and `join()`][approach-dict-and-join] +6. Using [`itertools.compress()`][approach-itertools-compress] +7. Using [`functools.reduce()`][approach-functools-reduce] + + +For our performance investigation, we'll also include an 8th approach that [uses Python 3.10's `structural pattern matching`][PEP0622]. + + +## Benchmarks + +To benchmark these functions, we wrote a small [benchmarking script][benchmark-application] using the [timeit][timeit] module along with third-party libraries [numpy][numpy] and [pandas][pandas]. + + +| | 10 | 14 | 15 | 70 | 105 | 182 | 189 | 203 | 204 | 399 | 409 | 525 | 735 | 1575 | 3250 | +|----------------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| if statements | 2.12e-07 | 2.16e-07 | 2.60e-07 | 2.76e-07 | 2.98e-07 | 2.10e-07 | 2.59e-07 | 2.15e-07 | 2.10e-07 | 2.75e-07 | 2.61e-07 | 3.41e-07 | 2.99e-07 | 2.98e-07 | 2.13e-07 | +| ternary with truthy/falsy | 2.65e-07 | 2.65e-07 | 2.80e-07 | 2.70e-07 | 2.91e-07 | 2.67e-07 | 2.73e-07 | 2.75e-07 | 2.69e-07 | 3.56e-07 | 2.02e-07 | 3.06e-07 | 3.06e-07 | 3.04e-07 | 2.89e-07 | +| loop with tuple | 4.01e-07 | 4.09e-07 | 5.05e-07 | 4.94e-07 | 6.48e-07 | 3.97e-07 | 5.25e-07 | 4.10e-07 | 2.04e-07 | 5.51e-07 | 4.06e-07 | 9.04e-07 | 6.16e-07 | 6.89e-07 | 4.33e-07 | +| structural pattern matching | 7.55e-07 | 7.31e-07 | 6.09e-07 | 5.87e-07 | 5.21e-07 | 7.11e-07 | 6.42e-07 | 7.19e-07 | 6.90e-07 | 6.49e-07 | 8.43e-07 | 5.00e-07 | 5.12e-07 | 5.21e-07 | 7.48e-07 | +| dictionary with join | 8.31e-07 | 8.18e-07 | 9.34e-07 | 1.02e-06 | 9.75e-07 | 8.55e-07 | 9.13e-07 | 8.25e-07 | 8.32e-07 | 2.28e-06 | 9.22e-07 | 1.05e-06 | 2.42e-06 | 9.94e-07 | 8.46e-07 | +| sequence with join | 8.29e-07 | 8.17e-07 | 9.27e-07 | 9.10e-07 | 9.62e-07 | 8.73e-07 | 9.70e-07 | 8.87e-07 | 9.40e-07 | 2.52e-06 | 9.74e-07 | 2.44e-06 | 2.57e-06 | 9.79e-07 | 8.67e-07 | +| itertools with join | 9.46e-07 | 9.33e-07 | 4.04e-07 | 9.88e-07 | 1.01e-06 | 9.41e-07 | 9.91e-07 | 9.65e-07 | 9.80e-07 | 2.51e-06 | 1.10e-06 | 2.50e-06 | 1.02e-06 | 1.00e-06 | 9.60e-07 | +| functools reduce | 1.35e-06 | 1.38e-06 | 1.41e-06 | 1.39e-06 | 1.48e-06 | 1.33e-06 | 1.42e-06 | 1.37e-06 | 1.34e-06 | 1.39e-06 | 1.43e-06 | 1.45e-06 | 1.46e-06 | 1.45e-06 | 1.37e-06 | + + +Keep in mind that all these approaches are _very fast_, and that benchmarking at this granularity can be unstable. +That caveat in mind, the two `if-statement` based approaches benchmark fastest, and the approach using `functools.reduce()` was the slowest. + +The 'recommended' approach came in 4th, though it can be argued that the slowdown is justified by the increased readability and maintainability. + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + + +[PEP0622]: https://peps.python.org/pep-0622/ +[approach-dict-and-join ]: https://exercism.org/tracks/python/exercises/raindrops/approaches/dict-and-join +[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/raindrops/approaches/functools-reduce +[approach-if-statements]: https://exercism.org/tracks/python/exercises/raindrops/approaches/if-statements +[approach-itertools-compress]: https://exercism.org/tracks/python/exercises/raindrops/approaches/itertools-compress +[approach-loop-and-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/loop-and-fstring +[approach-sequence-with-join]: https://exercism.org/tracks/python/exercises/raindrops/approaches/sequence-with-join +[approach-truthy-and-falsey-with-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/truthy-and-falsey-with-fstring +[approaches-intro]: https://exercism.org/tracks/python/exercises/raindrops/approaches/introduction.md +[benchmark-application]: https://exercism.org/tracks/python/exercises/raindrops/.articles/code/Benchmark.py +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[numpy]: https://numpy.org/ +[pandas]: https://pandas.pydata.org/ +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface diff --git a/exercises/practice/raindrops/.articles/performance/snippet.md b/exercises/practice/raindrops/.articles/performance/snippet.md new file mode 100644 index 0000000000..d3449e8656 --- /dev/null +++ b/exercises/practice/raindrops/.articles/performance/snippet.md @@ -0,0 +1,8 @@ +| | 10 | 14 | 15 | 70 | 105 | 182 | 189 | 203 | 204 | 399 | 409 | 525 | 735 | 1575 | 3250 | +|----------------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| if statements | 2.12e-07 | 2.16e-07 | 2.60e-07 | 2.76e-07 | 2.98e-07 | 2.10e-07 | 2.59e-07 | 2.15e-07 | 2.10e-07 | 2.75e-07 | 2.61e-07 | 3.41e-07 | 2.99e-07 | 2.98e-07 | 2.13e-07 | +| loop with tuple | 4.01e-07 | 4.09e-07 | 5.05e-07 | 4.94e-07 | 6.48e-07 | 3.97e-07 | 5.25e-07 | 4.10e-07 | 2.04e-07 | 5.51e-07 | 4.06e-07 | 9.04e-07 | 6.16e-07 | 6.89e-07 | 4.33e-07 | +| structural pattern matching | 7.55e-07 | 7.31e-07 | 6.09e-07 | 5.87e-07 | 5.21e-07 | 7.11e-07 | 6.42e-07 | 7.19e-07 | 6.90e-07 | 6.49e-07 | 8.43e-07 | 5.00e-07 | 5.12e-07 | 5.21e-07 | 7.48e-07 | +| dictionary with join | 8.31e-07 | 8.18e-07 | 9.34e-07 | 1.02e-06 | 9.75e-07 | 8.55e-07 | 9.13e-07 | 8.25e-07 | 8.32e-07 | 2.28e-06 | 9.22e-07 | 1.05e-06 | 2.42e-06 | 9.94e-07 | 8.46e-07 | +| itertools with join | 9.46e-07 | 9.33e-07 | 4.04e-07 | 9.88e-07 | 1.01e-06 | 9.41e-07 | 9.91e-07 | 9.65e-07 | 9.80e-07 | 2.51e-06 | 1.10e-06 | 2.50e-06 | 1.02e-06 | 1.00e-06 | 9.60e-07 | +| functools reduce | 1.35e-06 | 1.38e-06 | 1.41e-06 | 1.39e-06 | 1.48e-06 | 1.33e-06 | 1.42e-06 | 1.37e-06 | 1.34e-06 | 1.39e-06 | 1.43e-06 | 1.45e-06 | 1.46e-06 | 1.45e-06 | 1.37e-06 | \ No newline at end of file From f3016b0ca9dde4eed05b61ef428899b6ac46e444 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 1 Feb 2024 03:23:56 -0800 Subject: [PATCH 599/826] Fixed some typos. (#3612) --- exercises/practice/raindrops/.approaches/introduction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/raindrops/.approaches/introduction.md b/exercises/practice/raindrops/.approaches/introduction.md index 1783fa33ab..b55ceabecb 100644 --- a/exercises/practice/raindrops/.approaches/introduction.md +++ b/exercises/practice/raindrops/.approaches/introduction.md @@ -176,7 +176,7 @@ If the result of `itertools.compress` is empty, a string version of the input nu For more details, see the [itertools.compress][approach-itertools-compress] approach. -## Approach: Using `functools.reduce())` and `zip()` +## Approach: Using `functools.reduce()` and `zip()` ```python @@ -283,3 +283,4 @@ To compare the performance of this and the other approaches, take a look at the [remainder]: https://docs.python.org/3/library/math.html#math.remainder [ternary expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions [truth-value-testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing +[structural-pattern-matching]: https://peps.python.org/pep-0622/ From 7eae72e8de425874ee1d30f5fecaabd837d8a015 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Thu, 1 Feb 2024 14:11:20 +0000 Subject: [PATCH 600/826] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3613)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/bfc6fe525e057a43f41e4fd83dfac00569d14086 --- .github/workflows/no-important-files-changed.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 39a6551747..26b068bc46 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -57,6 +57,7 @@ jobs: if: steps.check.outputs.important_files_changed == 'true' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: + github-token: ${{ github.token }} script: | const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" github.rest.issues.createComment({ From 51f8c8dc2ea09921942321ae1271f0b237b8a659 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 2 Feb 2024 09:31:42 -0800 Subject: [PATCH 601/826] Corrected spelling error. (#3616) --- .../concept/mecha-munch-management/.docs/instructions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index f5fa6f7901..4c7661d64e 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -94,11 +94,11 @@ Create the function `sort_entries()` that takes a shopping cart/dictionary ## 5. Send User Shopping Cart to Store for Fulfillment The app needs to send a given users cart to the store for fulfillment. -However, the shoppers in the store need to know which store isle the item can be found in and if the item needs refrigeration. +However, the shoppers in the store need to know which store aisle the item can be found in and if the item needs refrigeration. So (_rather arbitrarily_) the "fulfillment cart" needs to be sorted in reverse alphabetical order with item quantities combined with location and refrigeration information. -Create the function `send_to_store(, )` that takes a user shopping cart and a dictionary that has store isle number and a `True`/`False` for refrigeration needed for each item. -The function should `return` a combined "fulfillment cart" that has (quantity, isle, and refrigeration) for each item the customer is ordering. +Create the function `send_to_store(, )` that takes a user shopping cart and a dictionary that has store aisle number and a `True`/`False` for refrigeration needed for each item. +The function should `return` a combined "fulfillment cart" that has (quantity, aisle, and refrigeration) for each item the customer is ordering. Items should appear in _reverse_ alphabetical order. ```python From cadb6120e04d460fd891f5cf98ed395630722f52 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 2 Feb 2024 10:05:24 -0800 Subject: [PATCH 602/826] Mark nucleotied-count as foregone. (#3617) --- config.json | 11 +---- .../nucleotide-count/.docs/instructions.md | 23 ---------- .../nucleotide-count/.meta/config.json | 32 ------------- .../nucleotide-count/.meta/example.py | 18 -------- .../nucleotide-count/.meta/tests.toml | 18 -------- .../nucleotide-count/nucleotide_count.py | 6 --- .../nucleotide-count/nucleotide_count_test.py | 46 ------------------- 7 files changed, 1 insertion(+), 153 deletions(-) delete mode 100644 exercises/practice/nucleotide-count/.docs/instructions.md delete mode 100644 exercises/practice/nucleotide-count/.meta/config.json delete mode 100644 exercises/practice/nucleotide-count/.meta/example.py delete mode 100644 exercises/practice/nucleotide-count/.meta/tests.toml delete mode 100644 exercises/practice/nucleotide-count/nucleotide_count.py delete mode 100644 exercises/practice/nucleotide-count/nucleotide_count_test.py diff --git a/config.json b/config.json index 754ca9f3fd..06f64077d9 100644 --- a/config.json +++ b/config.json @@ -2143,15 +2143,6 @@ "difficulty": 2, "status": "deprecated" }, - { - "slug": "nucleotide-count", - "name": "Nucleotide Count", - "uuid": "105f25ec-7ce2-4797-893e-05e3792ebd91", - "practices": [], - "prerequisites": [], - "difficulty": 2, - "status": "deprecated" - }, { "slug": "binary", "name": "Binary", @@ -2234,7 +2225,7 @@ "status": "deprecated" } ], - "foregone": ["lens-person", "parallel-letter-frequency"] + "foregone": ["lens-person", "nucleotide-count", "parallel-letter-frequency"] }, "concepts": [ { diff --git a/exercises/practice/nucleotide-count/.docs/instructions.md b/exercises/practice/nucleotide-count/.docs/instructions.md deleted file mode 100644 index 548d9ba5a5..0000000000 --- a/exercises/practice/nucleotide-count/.docs/instructions.md +++ /dev/null @@ -1,23 +0,0 @@ -# Instructions - -Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. -All known life depends on DNA! - -> Note: You do not need to understand anything about nucleotides or DNA to complete this exercise. - -DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. -A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! -We call the order of these nucleotides in a bit of DNA a "DNA sequence". - -We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides. -'A' for adenine, 'C' for cytosine, 'G' for guanine, and 'T' for thymine. - -Given a string representing a DNA sequence, count how many of each nucleotide is present. -If the string contains characters that aren't A, C, G, or T then it is invalid and you should signal an error. - -For example: - -```text -"GATTACA" -> 'A': 3, 'C': 1, 'G': 1, 'T': 2 -"INVALID" -> error -``` diff --git a/exercises/practice/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json deleted file mode 100644 index e0c108f7b8..0000000000 --- a/exercises/practice/nucleotide-count/.meta/config.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", - "authors": [], - "contributors": [ - "behrtam", - "cmccandless", - "Dog", - "ikhadykin", - "kytrinyx", - "lowks", - "mostlybadfly", - "N-Parsons", - "Oniwa", - "orozcoadrian", - "pheanex", - "sjakobi", - "tqa236" - ], - "files": { - "solution": [ - "nucleotide_count.py" - ], - "test": [ - "nucleotide_count_test.py" - ], - "example": [ - ".meta/example.py" - ] - }, - "source": "The Calculating DNA Nucleotides_problem at Rosalind", - "source_url": "https://rosalind.info/problems/dna/" -} diff --git a/exercises/practice/nucleotide-count/.meta/example.py b/exercises/practice/nucleotide-count/.meta/example.py deleted file mode 100644 index e79a6a7ec7..0000000000 --- a/exercises/practice/nucleotide-count/.meta/example.py +++ /dev/null @@ -1,18 +0,0 @@ -NUCLEOTIDES = 'ATCG' - - -def count(strand, abbreviation): - _validate(abbreviation) - return strand.count(abbreviation) - - -def nucleotide_counts(strand): - return { - abbr: strand.count(abbr) - for abbr in NUCLEOTIDES - } - - -def _validate(abbreviation): - if abbreviation not in NUCLEOTIDES: - raise ValueError(f'{abbreviation} is not a nucleotide.') diff --git a/exercises/practice/nucleotide-count/.meta/tests.toml b/exercises/practice/nucleotide-count/.meta/tests.toml deleted file mode 100644 index 79b22f7a85..0000000000 --- a/exercises/practice/nucleotide-count/.meta/tests.toml +++ /dev/null @@ -1,18 +0,0 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. - -[3e5c30a8-87e2-4845-a815-a49671ade970] -description = "empty strand" - -[a0ea42a6-06d9-4ac6-828c-7ccaccf98fec] -description = "can count one nucleotide in single-character input" - -[eca0d565-ed8c-43e7-9033-6cefbf5115b5] -description = "strand with repeated nucleotide" - -[40a45eac-c83f-4740-901a-20b22d15a39f] -description = "strand with multiple nucleotides" - -[b4c47851-ee9e-4b0a-be70-a86e343bd851] -description = "strand with invalid nucleotides" diff --git a/exercises/practice/nucleotide-count/nucleotide_count.py b/exercises/practice/nucleotide-count/nucleotide_count.py deleted file mode 100644 index 7f794acbfb..0000000000 --- a/exercises/practice/nucleotide-count/nucleotide_count.py +++ /dev/null @@ -1,6 +0,0 @@ -def count(strand, nucleotide): - pass - - -def nucleotide_counts(strand): - pass diff --git a/exercises/practice/nucleotide-count/nucleotide_count_test.py b/exercises/practice/nucleotide-count/nucleotide_count_test.py deleted file mode 100644 index bd5b5bddcb..0000000000 --- a/exercises/practice/nucleotide-count/nucleotide_count_test.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Tests for the nucleotide-count exercise - -Implementation note: -The count function must raise a ValueError with a meaningful error message -in case of a bad argument. -""" -import unittest - -from nucleotide_count import count, nucleotide_counts - - -class NucleotideCountTest(unittest.TestCase): - def test_empty_dna_string_has_no_adenosine(self): - self.assertEqual(count('', 'A'), 0) - - def test_empty_dna_string_has_no_nucleotides(self): - expected = {'A': 0, 'T': 0, 'C': 0, 'G': 0} - self.assertEqual(nucleotide_counts(""), expected) - - def test_repetitive_cytidine_gets_counted(self): - self.assertEqual(count('CCCCC', 'C'), 5) - - def test_repetitive_sequence_has_only_guanosine(self): - expected = {'A': 0, 'T': 0, 'C': 0, 'G': 8} - self.assertEqual(nucleotide_counts('GGGGGGGG'), expected) - - def test_counts_only_thymidine(self): - self.assertEqual(count('GGGGGTAACCCGG', 'T'), 1) - - def test_validates_nucleotides(self): - with self.assertRaisesWithMessage(ValueError): - count("GACT", 'X') - - def test_counts_all_nucleotides(self): - dna = ('AGCTTTTCATTCTGACTGCAACGGGCAATATGTCT' - 'CTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC') - expected = {'A': 20, 'T': 21, 'G': 17, 'C': 12} - self.assertEqual(nucleotide_counts(dna), expected) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == '__main__': - unittest.main() From 59a459abe8ee5e7f2fe69040d4d020284aa6d1a8 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Fri, 2 Feb 2024 18:05:54 +0000 Subject: [PATCH 603/826] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/f72e90478cac439f8ded661b9b650dd923898985 --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index df8e36761c..3f7813de10 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -90,4 +90,4 @@ This policy was initially adopted from the Front-end London Slack community and A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). _This policy is a "living" document, and subject to refinement and expansion in the future. -This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._ +This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._ From 98e9bfeede05d0c24adb00a74ad2f7ea21d52e3e Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 2 Feb 2024 19:34:26 +0100 Subject: [PATCH 604/826] `pop-count`: rename to `eliuds-eggs` (#3615) * Rename the `pop-count` exercise to `eliuds-eggs`. * Regenerated test file and renamed stub and test file. * Changed exercise config file to match change in sub and test file naming. * Corrected typo in exercise config for test file location. --------- Co-authored-by: BethanyG --- config.json | 2 +- .../{pop-count => eliuds-eggs}/.docs/instructions.md | 0 .../{pop-count => eliuds-eggs}/.docs/introduction.md | 0 .../practice/{pop-count => eliuds-eggs}/.meta/config.json | 4 ++-- .../practice/{pop-count => eliuds-eggs}/.meta/example.py | 0 .../practice/{pop-count => eliuds-eggs}/.meta/template.j2 | 0 .../practice/{pop-count => eliuds-eggs}/.meta/tests.toml | 0 .../pop_count.py => eliuds-eggs/eliuds_eggs.py} | 0 .../pop_count_test.py => eliuds-eggs/eliuds_eggs_test.py} | 8 ++++---- 9 files changed, 7 insertions(+), 7 deletions(-) rename exercises/practice/{pop-count => eliuds-eggs}/.docs/instructions.md (100%) rename exercises/practice/{pop-count => eliuds-eggs}/.docs/introduction.md (100%) rename exercises/practice/{pop-count => eliuds-eggs}/.meta/config.json (89%) rename exercises/practice/{pop-count => eliuds-eggs}/.meta/example.py (100%) rename exercises/practice/{pop-count => eliuds-eggs}/.meta/template.j2 (100%) rename exercises/practice/{pop-count => eliuds-eggs}/.meta/tests.toml (100%) rename exercises/practice/{pop-count/pop_count.py => eliuds-eggs/eliuds_eggs.py} (100%) rename exercises/practice/{pop-count/pop_count_test.py => eliuds-eggs/eliuds_eggs_test.py} (79%) diff --git a/config.json b/config.json index 06f64077d9..99d23d77c3 100644 --- a/config.json +++ b/config.json @@ -967,7 +967,7 @@ "difficulty": 2 }, { - "slug": "pop-count", + "slug": "eliuds-eggs", "name": "Eliud's Eggs", "uuid": "356e2d29-7efc-4fa3-bec7-8b61c3e967da", "practices": ["loops"], diff --git a/exercises/practice/pop-count/.docs/instructions.md b/exercises/practice/eliuds-eggs/.docs/instructions.md similarity index 100% rename from exercises/practice/pop-count/.docs/instructions.md rename to exercises/practice/eliuds-eggs/.docs/instructions.md diff --git a/exercises/practice/pop-count/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md similarity index 100% rename from exercises/practice/pop-count/.docs/introduction.md rename to exercises/practice/eliuds-eggs/.docs/introduction.md diff --git a/exercises/practice/pop-count/.meta/config.json b/exercises/practice/eliuds-eggs/.meta/config.json similarity index 89% rename from exercises/practice/pop-count/.meta/config.json rename to exercises/practice/eliuds-eggs/.meta/config.json index 0c402acfad..a230751738 100644 --- a/exercises/practice/pop-count/.meta/config.json +++ b/exercises/practice/eliuds-eggs/.meta/config.json @@ -4,10 +4,10 @@ ], "files": { "solution": [ - "pop_count.py" + "eliuds_eggs.py" ], "test": [ - "pop_count_test.py" + "eliuds_eggs_test.py" ], "example": [ ".meta/example.py" diff --git a/exercises/practice/pop-count/.meta/example.py b/exercises/practice/eliuds-eggs/.meta/example.py similarity index 100% rename from exercises/practice/pop-count/.meta/example.py rename to exercises/practice/eliuds-eggs/.meta/example.py diff --git a/exercises/practice/pop-count/.meta/template.j2 b/exercises/practice/eliuds-eggs/.meta/template.j2 similarity index 100% rename from exercises/practice/pop-count/.meta/template.j2 rename to exercises/practice/eliuds-eggs/.meta/template.j2 diff --git a/exercises/practice/pop-count/.meta/tests.toml b/exercises/practice/eliuds-eggs/.meta/tests.toml similarity index 100% rename from exercises/practice/pop-count/.meta/tests.toml rename to exercises/practice/eliuds-eggs/.meta/tests.toml diff --git a/exercises/practice/pop-count/pop_count.py b/exercises/practice/eliuds-eggs/eliuds_eggs.py similarity index 100% rename from exercises/practice/pop-count/pop_count.py rename to exercises/practice/eliuds-eggs/eliuds_eggs.py diff --git a/exercises/practice/pop-count/pop_count_test.py b/exercises/practice/eliuds-eggs/eliuds_eggs_test.py similarity index 79% rename from exercises/practice/pop-count/pop_count_test.py rename to exercises/practice/eliuds-eggs/eliuds_eggs_test.py index c3ca192156..d093e023be 100644 --- a/exercises/practice/pop-count/pop_count_test.py +++ b/exercises/practice/eliuds-eggs/eliuds_eggs_test.py @@ -1,15 +1,15 @@ # These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/pop-count/canonical-data.json -# File last updated on 2023-10-18 +# https://github.com/exercism/problem-specifications/tree/main/exercises/eliuds-eggs/canonical-data.json +# File last updated on 2024-02-02 import unittest -from pop_count import ( +from eliuds_eggs import ( egg_count, ) -class PopCountTest(unittest.TestCase): +class EliudsEggsTest(unittest.TestCase): def test_0_eggs(self): expected = 0 self.assertEqual(egg_count(0), expected) From 9d7aba46b650e36c1fa5740f3fa203cb8d3d8bcb Mon Sep 17 00:00:00 2001 From: izmirli Date: Fri, 2 Feb 2024 23:57:47 +0200 Subject: [PATCH 605/826] [Inventory Management] New test for decrement_items not in inventory (#3618) [no important files changed] --- .../concept/inventory-management/dicts_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/exercises/concept/inventory-management/dicts_test.py b/exercises/concept/inventory-management/dicts_test.py index f7faba35a5..71a8ff2b72 100644 --- a/exercises/concept/inventory-management/dicts_test.py +++ b/exercises/concept/inventory-management/dicts_test.py @@ -77,6 +77,19 @@ def test_not_below_zero(self): self.assertEqual(actual_result, expected, msg=error_message) + @pytest.mark.task(taskno=3) + def test_decrement_items_not_in_inventory(self): + actual_result = decrement_items({"iron": 3, "gold": 2}, + ["iron", "wood", "iron", "diamond"]) + + expected = {"iron": 1, "gold": 2} + error_message = ('Called decrement_items({"iron": 3, "gold": 2}, ' + '["iron", "wood", "iron", "diamond"]). The function ' + f'returned {actual_result}, but the tests ' + f'expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) + @pytest.mark.task(taskno=4) def test_remove_item(self): actual_result = remove_item({"iron": 1, "diamond": 2, "gold": 1}, "diamond") From d22877f62bb1a4581df45bd2d76d5d7682875b25 Mon Sep 17 00:00:00 2001 From: Ryan Hartlage Date: Sun, 4 Feb 2024 12:16:34 -0500 Subject: [PATCH 606/826] Fix misspelling of 'aisle' as 'isle' (#3619) --- .../.docs/instructions.md | 16 ++--- .../mecha-munch-management/.meta/exemplar.py | 8 +-- .../mecha-munch-management/dict_methods.py | 6 +- .../dict_methods_test.py | 70 +++++++++---------- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index 4c7661d64e..c7cf733276 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -17,11 +17,11 @@ Create the function `add_items(, )` that takes a car It should return a new/updated shopping cart dictionary for the user. ```python ->>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, +>>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, ('Apple', 'Apple', 'Orange', 'Apple', 'Banana')) {'Banana': 4, 'Apple': 5, 'Orange': 2} ->>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, +>>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, ['Banana', 'Orange', 'Blueberries', 'Banana']) {'Banana': 5, 'Apple': 2, 'Orange': 2, 'Blueberries': 1} ``` @@ -102,9 +102,9 @@ The function should `return` a combined "fulfillment cart" that has (quantity, a Items should appear in _reverse_ alphabetical order. ```python ->>> send_to_store({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, - {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}) -{'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]} +>>> send_to_store({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, + {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}) +{'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]} ``` ## 6. Update the Store Inventory to Reflect what a User Has Ordered. @@ -119,10 +119,10 @@ The function should reduce the store inventory amounts by the number "ordered" i Where a store item count falls to 0, the count should be replaced by the message 'Out of Stock'. ```python ->>> update_store_inventory({'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, -{'Banana': [15, 'Isle 5', False], 'Apple': [12, 'Isle 4', False], 'Orange': [1, 'Isle 4', False], 'Milk': [4, 'Isle 2', True]}) +>>> update_store_inventory({'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, +{'Banana': [15, 'Aisle 5', False], 'Apple': [12, 'Aisle 4', False], 'Orange': [1, 'Aisle 4', False], 'Milk': [4, 'Aisle 2', True]}) -{'Banana': [12, 'Isle 5', False], 'Apple': [10, 'Isle 4', False], 'Orange': ['Out of Stock', 'Isle 4', False], 'Milk': [2, 'Isle 2', True]} +{'Banana': [12, 'Aisle 5', False], 'Apple': [10, 'Aisle 4', False], 'Orange': ['Out of Stock', 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True]} ``` [feature creep]: https://en.wikipedia.org/wiki/Feature_creep diff --git a/exercises/concept/mecha-munch-management/.meta/exemplar.py b/exercises/concept/mecha-munch-management/.meta/exemplar.py index 0390944dbb..ea25110a3b 100644 --- a/exercises/concept/mecha-munch-management/.meta/exemplar.py +++ b/exercises/concept/mecha-munch-management/.meta/exemplar.py @@ -48,17 +48,17 @@ def sort_entries(cart): return dict(sorted(cart.items())) -def send_to_store(cart, isle_mapping): - """Combine users order to isle and refrigeration information. +def send_to_store(cart, aisle_mapping): + """Combine users order to aisle and refrigeration information. :param cart: dict - users shopping cart dictionary. - :param isle_mapping: dict - isle and refrigeration information dictionary. + :param aisle_mapping: dict - aisle and refrigeration information dictionary. :return: dict - fulfillment dictionary ready to send to store. """ fulfillment_cart = {} for key in cart.keys(): - fulfillment_cart[key] = [cart[key]] + isle_mapping[key] + fulfillment_cart[key] = [cart[key]] + aisle_mapping[key] return dict(sorted(fulfillment_cart.items(), reverse=True)) diff --git a/exercises/concept/mecha-munch-management/dict_methods.py b/exercises/concept/mecha-munch-management/dict_methods.py index d443c8bca5..f502fe00ab 100644 --- a/exercises/concept/mecha-munch-management/dict_methods.py +++ b/exercises/concept/mecha-munch-management/dict_methods.py @@ -43,11 +43,11 @@ def sort_entries(cart): pass -def send_to_store(cart, isle_mapping): - """Combine users order to isle and refrigeration information. +def send_to_store(cart, aisle_mapping): + """Combine users order to aisle and refrigeration information. :param cart: dict - users shopping cart dictionary. - :param isle_mapping: dict - isle and refrigeration information dictionary. + :param aisle_mapping: dict - aisle and refrigeration information dictionary. :return: dict - fulfillment dictionary ready to send to store. """ diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 2c9e36948c..8ec0211d53 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -122,31 +122,31 @@ def test_sort_entries(self): def test_send_to_store(self): input_data = [ ({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, - {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], - 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}), + {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], + 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), ({'Kiwi': 3, 'Juice': 5, 'Yoghurt': 2, 'Milk': 5}, - {'Kiwi': ['Isle 6', False], 'Juice': ['Isle 5', False], - 'Yoghurt': ['Isle 2', True], 'Milk': ['Isle 2', True]}), + {'Kiwi': ['Aisle 6', False], 'Juice': ['Aisle 5', False], + 'Yoghurt': ['Aisle 2', True], 'Milk': ['Aisle 2', True]}), ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4}, - {'Apple': ['Isle 1', False], 'Raspberry': ['Isle 6', False], - 'Blueberries': ['Isle 6', False], 'Broccoli': ['Isle 3', False], - 'Kiwi': ['Isle 6', False], 'Melon': ['Isle 6', False]}) + {'Apple': ['Aisle 1', False], 'Raspberry': ['Aisle 6', False], + 'Blueberries': ['Aisle 6', False], 'Broccoli': ['Aisle 3', False], + 'Kiwi': ['Aisle 6', False], 'Melon': ['Aisle 6', False]}) ] output_data = [ - {'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], - 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, + {'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], + 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, - {'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True], - 'Kiwi': [3, 'Isle 6', False], 'Juice': [5, 'Isle 5', False]}, + {'Yoghurt': [2, 'Aisle 2', True], 'Milk': [5, 'Aisle 2', True], + 'Kiwi': [3, 'Aisle 6', False], 'Juice': [5, 'Aisle 5', False]}, - {'Raspberry': [2, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], - 'Kiwi': [1, 'Isle 6', False], 'Broccoli': [2, 'Isle 3', False], - 'Blueberries': [5, 'Isle 6', False], 'Apple': [2, 'Isle 1', False]} + {'Raspberry': [2, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], + 'Kiwi': [1, 'Aisle 6', False], 'Broccoli': [2, 'Aisle 3', False], + 'Blueberries': [5, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False]} ] for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): @@ -164,32 +164,32 @@ def test_send_to_store(self): @pytest.mark.task(taskno=6) def test_update_store_inventory(self): input_data = [ - ({'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], - 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, - {'Banana': [15, 'Isle 5', False], 'Apple': [12, 'Isle 4', False], - 'Orange': [1, 'Isle 4', False], 'Milk': [4, 'Isle 2', True]}), - - ({'Kiwi': [3, 'Isle 6', False]},{'Kiwi': [3, 'Isle 6', False], 'Juice': [5, 'Isle 5', False], - 'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True]}), - - ({'Kiwi': [1, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], 'Apple': [2, 'Isle 1', False], - 'Raspberry': [2, 'Isle 6', False], 'Blueberries': [5, 'Isle 6', False], - 'Broccoli': [1, 'Isle 3', False]}, - {'Apple': [2, 'Isle 1', False], 'Raspberry': [5, 'Isle 6', False], - 'Blueberries': [10, 'Isle 6', False], 'Broccoli': [4, 'Isle 3', False], - 'Kiwi': [1, 'Isle 6', False], 'Melon': [8, 'Isle 6', False]}) + ({'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], + 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, + {'Banana': [15, 'Aisle 5', False], 'Apple': [12, 'Aisle 4', False], + 'Orange': [1, 'Aisle 4', False], 'Milk': [4, 'Aisle 2', True]}), + + ({'Kiwi': [3, 'Aisle 6', False]},{'Kiwi': [3, 'Aisle 6', False], 'Juice': [5, 'Aisle 5', False], + 'Yoghurt': [2, 'Aisle 2', True], 'Milk': [5, 'Aisle 2', True]}), + + ({'Kiwi': [1, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False], + 'Raspberry': [2, 'Aisle 6', False], 'Blueberries': [5, 'Aisle 6', False], + 'Broccoli': [1, 'Aisle 3', False]}, + {'Apple': [2, 'Aisle 1', False], 'Raspberry': [5, 'Aisle 6', False], + 'Blueberries': [10, 'Aisle 6', False], 'Broccoli': [4, 'Aisle 3', False], + 'Kiwi': [1, 'Aisle 6', False], 'Melon': [8, 'Aisle 6', False]}) ] output_data = [ - {'Banana': [12, 'Isle 5', False], 'Apple': [10, 'Isle 4', False], - 'Orange': ['Out of Stock', 'Isle 4', False], 'Milk': [2, 'Isle 2', True]}, + {'Banana': [12, 'Aisle 5', False], 'Apple': [10, 'Aisle 4', False], + 'Orange': ['Out of Stock', 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True]}, - {'Juice': [5, 'Isle 5', False], 'Yoghurt': [2, 'Isle 2', True], - 'Milk': [5, 'Isle 2', True], 'Kiwi': ["Out of Stock", 'Isle 6', False]}, + {'Juice': [5, 'Aisle 5', False], 'Yoghurt': [2, 'Aisle 2', True], + 'Milk': [5, 'Aisle 2', True], 'Kiwi': ["Out of Stock", 'Aisle 6', False]}, - {'Kiwi': ['Out of Stock', 'Isle 6', False], 'Melon': [4, 'Isle 6', False], - 'Apple': ['Out of Stock', 'Isle 1', False], 'Raspberry': [3, 'Isle 6', False], - 'Blueberries': [5, 'Isle 6', False], 'Broccoli': [3, 'Isle 3', False]} + {'Kiwi': ['Out of Stock', 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], + 'Apple': ['Out of Stock', 'Aisle 1', False], 'Raspberry': [3, 'Aisle 6', False], + 'Blueberries': [5, 'Aisle 6', False], 'Broccoli': [3, 'Aisle 3', False]} ] for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): From 5c7eb1defb93e0e90a16d219f733b12a0824d890 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 6 Feb 2024 19:49:58 +0100 Subject: [PATCH 607/826] roman-numerals: sync (#3622) * Sync the `roman-numerals` exercise's docs with the latest data. * Sync the `roman-numerals` exercise's metadata with the latest data. --- .../roman-numerals/.docs/instructions.md | 45 +++----------- .../roman-numerals/.docs/introduction.md | 59 +++++++++++++++++++ .../practice/roman-numerals/.meta/config.json | 2 +- 3 files changed, 68 insertions(+), 38 deletions(-) create mode 100644 exercises/practice/roman-numerals/.docs/introduction.md diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md index 247ea0892e..50e2f5bf1c 100644 --- a/exercises/practice/roman-numerals/.docs/instructions.md +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -1,41 +1,12 @@ -# Instructions +# Introduction -Write a function to convert from normal numbers to Roman Numerals. +Your task is to convert a number from Arabic numerals to Roman numerals. -The Romans were a clever bunch. -They conquered most of Europe and ruled it for hundreds of years. -They invented concrete and straight roads and even bikinis. -One thing they never discovered though was the number zero. -This made writing and dating extensive histories of their exploits slightly more challenging, but the system of numbers they came up with is still in use today. -For example the BBC uses Roman numerals to date their programs. +For this exercise, we are only concerned about traditional Roman numerals, in which the largest number is MMMCMXCIX (or 3,999). -The Romans wrote numbers using letters - I, V, X, L, C, D, M. -(notice these letters have lots of straight lines and are hence easy to hack into stone tablets). +~~~~exercism/note +There are lots of different ways to convert between Arabic and Roman numerals. +We recommend taking a naive approach first to familiarise yourself with the concept of Roman numerals and then search for more efficient methods. -```text - 1 => I -10 => X - 7 => VII -``` - -The maximum number supported by this notation is 3,999. -(The Romans themselves didn't tend to go any higher) - -Wikipedia says: Modern Roman numerals ... are written by expressing each digit separately starting with the left most digit and skipping any digit with a value of zero. - -To see this in practice, consider the example of 1990. - -In Roman numerals 1990 is MCMXC: - -1000=M -900=CM -90=XC - -2008 is written as MMVIII: - -2000=MM -8=VIII - -Learn more about [Roman numerals on Wikipedia][roman-numerals]. - -[roman-numerals]: https://wiki.imperivm-romanvm.com/wiki/Roman_Numerals +Make sure to check out our Deep Dive video at the end to explore the different approaches you can take! +~~~~ diff --git a/exercises/practice/roman-numerals/.docs/introduction.md b/exercises/practice/roman-numerals/.docs/introduction.md new file mode 100644 index 0000000000..6fd942fef3 --- /dev/null +++ b/exercises/practice/roman-numerals/.docs/introduction.md @@ -0,0 +1,59 @@ +# Description + +Today, most people in the world use Arabic numerals (0–9). +But if you travelled back two thousand years, you'd find that most Europeans were using Roman numerals instead. + +To write a Roman numeral we use the following Latin letters, each of which has a value: + +| M | D | C | L | X | V | I | +| ---- | --- | --- | --- | --- | --- | --- | +| 1000 | 500 | 100 | 50 | 10 | 5 | 1 | + +A Roman numeral is a sequence of these letters, and its value is the sum of the letters' values. +For example, `XVIII` has the value 18 (`10 + 5 + 1 + 1 + 1 = 18`). + +There's one rule that makes things trickier though, and that's that **the same letter cannot be used more than three times in succession**. +That means that we can't express numbers such as 4 with the seemingly natural `IIII`. +Instead, for those numbers, we use a subtraction method between two letters. +So we think of `4` not as `1 + 1 + 1 + 1` but instead as `5 - 1`. +And slightly confusingly to our modern thinking, we write the smaller number first. +This applies only in the following cases: 4 (`IV`), 9 (`IX`), 40 (`XL`), 90 (`XC`), 400 (`CD`) and 900 (`CM`). + +Order matters in Roman numerals! +Letters (and the special compounds above) must be ordered by decreasing value from left to right. + +Here are some examples: + +```text + 105 => CV +---- => -- + 100 => C ++ 5 => V +``` + +```text + 106 => CVI +---- => -- + 100 => C ++ 5 => V ++ 1 => I +``` + +```text + 104 => CIV +---- => --- + 100 => C ++ 4 => IV +``` + +And a final more complex example: + +```text + 1996 => MCMXCVI +----- => ------- + 1000 => M ++ 900 => CM ++ 90 => XC ++ 5 => V ++ 1 => I +``` diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index eab034a9f9..e66d34f29d 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -26,7 +26,7 @@ ".meta/example.py" ] }, - "blurb": "Write a function to convert from normal numbers to Roman Numerals.", + "blurb": "Convert modern Arabic numbers into Roman numerals.", "source": "The Roman Numeral Kata", "source_url": "https://codingdojo.org/kata/RomanNumerals/" } From 51f379877c18e283407267c1d10082093541d19c Mon Sep 17 00:00:00 2001 From: Tomasz Gieorgijewski <24649931+Nerwosolek@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:57:26 +0100 Subject: [PATCH 608/826] dict-and-join simplify for raindrops approaches (#3620) * dict-and-join simplify * Swapped dictionary approaches Swapped the two dictionary approaches and adjusted `introduction.md`, `Benchmark.py`, `Performance`, and the approaches doc accordingly. --------- Co-authored-by: BethanyG --- .../.approaches/dict-and-join/content.md | 8 ++++---- .../.approaches/dict-and-join/snippet.txt | 2 +- .../raindrops/.approaches/introduction.md | 8 ++++---- .../.articles/performance/code/Benchmark.py | 15 +++++++++++++++ .../.articles/performance/content.md | 19 ++++++++++--------- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/exercises/practice/raindrops/.approaches/dict-and-join/content.md b/exercises/practice/raindrops/.approaches/dict-and-join/content.md index ae302ada8d..94be03cb0a 100644 --- a/exercises/practice/raindrops/.approaches/dict-and-join/content.md +++ b/exercises/practice/raindrops/.approaches/dict-and-join/content.md @@ -6,15 +6,15 @@ def convert(number): sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} results = ''.join(sounds[divisor] for - divisor, sound in sounds.items() + divisor in sounds.keys() if number % divisor == 0) return results or str(number) ``` This approach uses a [dictionary][dict] called 'sounds' with factors as `keys` and sound strings as `values`. -A [generator-expression][generator-expressions] inside of [`str.join()`][str.join] loops through the [dictionary view object][dict-view-object] [`sounds.items()`][dict.items()], which is a series of (key, value) `tuples`. - Each `value` is looked up for every factor where number % divisor == 0. +A [generator-expression][generator-expressions] inside of [`str.join()`][str.join] loops through the [dictionary view object][dict-view-object] [`sounds.keys()`][dict.keys()], which is a sequence of all the dictionary keys. + Each `value` is looked up for every factor (key) where `number % divisor == 0`. `str.join()` then compiles the results. This is the equivalent of: @@ -46,7 +46,7 @@ def convert(number): ``` [dict-view-object]: https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects -[dict.items()]: https://docs.python.org/3/library/stdtypes.html#dict.items +[dict.keys()]: https://docs.python.org/3/library/stdtypes.html#dict.keys [dict]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [generator-expressions]: https://www.pythonmorsels.com/how-write-generator-expression/ [str.join]: https://docs.python.org/3/library/stdtypes.html#str.join diff --git a/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt b/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt index 6c24abb8df..662c7c6e54 100644 --- a/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt +++ b/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt @@ -2,7 +2,7 @@ def convert(number): sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} results = ''.join(sounds[divisor] for - divisor, sound in sounds.items() + divisor in sounds.keys() if number % divisor == 0) return results or str(number) diff --git a/exercises/practice/raindrops/.approaches/introduction.md b/exercises/practice/raindrops/.approaches/introduction.md index b55ceabecb..50e83d706e 100644 --- a/exercises/practice/raindrops/.approaches/introduction.md +++ b/exercises/practice/raindrops/.approaches/introduction.md @@ -144,7 +144,7 @@ def convert(number): 7: 'Plong'} results = ''.join(sounds[divisor] for - divisor, sound in sounds.items() + divisor in sounds.keys() if number % divisor == 0) return results or str(number) @@ -248,8 +248,8 @@ def convert(number): 5: 'Plang', 7: 'Plong'} - results = (sounds[divisor] for - divisor in sounds.keys() + results = (sound for + divisor, sound in sounds.items() if number % divisor == 0) return ''.join(results) or str(number) @@ -258,7 +258,7 @@ def convert(number): This separates the code that calculates the results string from the factors themselves. If a factor needs to be added, only the dictionary needs to be touched. -This code need only iterate over the keys of the dictionary to do its calculation, making this `O(1)` in time complexity. +This code need only iterate over the items of the dictionary to do its calculation, making this `O(1)` in time complexity. This does take `O(m)` space, where `m` is equal to the number of factor entries. Since the number of factors is fixed here, this is unlikely to create issues unless a great many more are added to the 'sounds' `dict`. diff --git a/exercises/practice/raindrops/.articles/performance/code/Benchmark.py b/exercises/practice/raindrops/.articles/performance/code/Benchmark.py index a01978a7ef..b30a091e60 100644 --- a/exercises/practice/raindrops/.articles/performance/code/Benchmark.py +++ b/exercises/practice/raindrops/.articles/performance/code/Benchmark.py @@ -63,6 +63,19 @@ def convert_dict(number): 7: 'Plong'} results = ''.join(sounds[divisor] for + divisor in sounds.keys() + if number % divisor == 0) + + return results or str(number) + + +def convert_dict_recommended(number): + + sounds = {3: 'Pling', + 5: 'Plang', + 7: 'Plong'} + + results = ''.join(sound for divisor, sound in sounds.items() if number % divisor == 0) @@ -127,6 +140,7 @@ def convert_pattern_matching(number): "loop with tuple", "sequence with join", "dictionary with join", + "dictionary recommended" "itertools with join", "functools reduce", "structural pattern matching"] @@ -140,6 +154,7 @@ def convert_pattern_matching(number): convert_loop, convert_sequence_join, convert_dict, + convert_dict_recommended, convert_itertools, convert_functools, convert_pattern_matching] diff --git a/exercises/practice/raindrops/.articles/performance/content.md b/exercises/practice/raindrops/.articles/performance/content.md index 522ff1016e..7894d2f94c 100644 --- a/exercises/practice/raindrops/.articles/performance/content.md +++ b/exercises/practice/raindrops/.articles/performance/content.md @@ -23,20 +23,21 @@ To benchmark these functions, we wrote a small [benchmarking script][benchmark-a | | 10 | 14 | 15 | 70 | 105 | 182 | 189 | 203 | 204 | 399 | 409 | 525 | 735 | 1575 | 3250 | |----------------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | -| if statements | 2.12e-07 | 2.16e-07 | 2.60e-07 | 2.76e-07 | 2.98e-07 | 2.10e-07 | 2.59e-07 | 2.15e-07 | 2.10e-07 | 2.75e-07 | 2.61e-07 | 3.41e-07 | 2.99e-07 | 2.98e-07 | 2.13e-07 | -| ternary with truthy/falsy | 2.65e-07 | 2.65e-07 | 2.80e-07 | 2.70e-07 | 2.91e-07 | 2.67e-07 | 2.73e-07 | 2.75e-07 | 2.69e-07 | 3.56e-07 | 2.02e-07 | 3.06e-07 | 3.06e-07 | 3.04e-07 | 2.89e-07 | -| loop with tuple | 4.01e-07 | 4.09e-07 | 5.05e-07 | 4.94e-07 | 6.48e-07 | 3.97e-07 | 5.25e-07 | 4.10e-07 | 2.04e-07 | 5.51e-07 | 4.06e-07 | 9.04e-07 | 6.16e-07 | 6.89e-07 | 4.33e-07 | -| structural pattern matching | 7.55e-07 | 7.31e-07 | 6.09e-07 | 5.87e-07 | 5.21e-07 | 7.11e-07 | 6.42e-07 | 7.19e-07 | 6.90e-07 | 6.49e-07 | 8.43e-07 | 5.00e-07 | 5.12e-07 | 5.21e-07 | 7.48e-07 | -| dictionary with join | 8.31e-07 | 8.18e-07 | 9.34e-07 | 1.02e-06 | 9.75e-07 | 8.55e-07 | 9.13e-07 | 8.25e-07 | 8.32e-07 | 2.28e-06 | 9.22e-07 | 1.05e-06 | 2.42e-06 | 9.94e-07 | 8.46e-07 | -| sequence with join | 8.29e-07 | 8.17e-07 | 9.27e-07 | 9.10e-07 | 9.62e-07 | 8.73e-07 | 9.70e-07 | 8.87e-07 | 9.40e-07 | 2.52e-06 | 9.74e-07 | 2.44e-06 | 2.57e-06 | 9.79e-07 | 8.67e-07 | -| itertools with join | 9.46e-07 | 9.33e-07 | 4.04e-07 | 9.88e-07 | 1.01e-06 | 9.41e-07 | 9.91e-07 | 9.65e-07 | 9.80e-07 | 2.51e-06 | 1.10e-06 | 2.50e-06 | 1.02e-06 | 1.00e-06 | 9.60e-07 | -| functools reduce | 1.35e-06 | 1.38e-06 | 1.41e-06 | 1.39e-06 | 1.48e-06 | 1.33e-06 | 1.42e-06 | 1.37e-06 | 1.34e-06 | 1.39e-06 | 1.43e-06 | 1.45e-06 | 1.46e-06 | 1.45e-06 | 1.37e-06 | +| if statements | 2.15e-07 | 2.13e-07 | 2.63e-07 | 2.62e-07 | 3.03e-07 | 2.13e-07 | 2.69e-07 | 2.12e-07 | 2.12e-07 | 2.75e-07 | 2.64e-07 | 3.04e-07 | 3.04e-07 | 3.06e-07 | 2.20e-07 | +| ternary with truthy/falsy | 2.79e-07 | 2.80e-07 | 2.89e-07 | 2.85e-07 | 3.02e-07 | 2.80e-07 | 3.13e-07 | 2.81e-07 | 2.79e-07 | 2.82e-07 | 3.30e-07 | 3.02e-07 | 3.02e-07 | 3.02e-07 | 2.80e-07 | +| loop with tuple | 7.91e-07 | 4.08e-07 | 5.07e-07 | 5.14e-07 | 6.13e-07 | 7.95e-07 | 5.10e-07 | 2.01e-07 | 8.18e-07 | 5.06e-07 | 3.85e-07 | 6.25e-07 | 6.10e-07 | 6.10e-07 | 2.00e-07 | +| structural pattern matching | 7.91e-07 | 7.39e-07 | 5.98e-07 | 6.24e-07 | 5.40e-07 | 7.47e-07 | 6.73e-07 | 7.72e-07 | 7.15e-07 | 6.69e-07 | 8.80e-07 | 5.43e-07 | 5.38e-07 | 5.69e-07 | 8.04e-07 | +| dictionary with join | 8.04e-07 | 7.98e-07 | 8.95e-07 | 9.81e-07 | 4.07e-07 | 8.15e-07 | 9.05e-07 | 8.14e-07 | 8.18e-07 | 9.05e-07 | 8.94e-07 | 9.51e-07 | 4.05e-07 | 9.80e-07 | 8.17e-07 | +| dictionary recommended | 8.55e-07 | 8.13e-07 | 8.99e-07 | 8.96e-07 | 9.36e-07 | 8.12e-07 | 9.27e-07 | 8.18e-07 | 8.24e-07 | 9.07e-07 | 9.13e-07 | 9.40e-07 | 9.36e-07 | 9.30e-07 | 8.15e-07 | +| sequence with  join | 8.59e-07 | 8.67e-07 | 9.56e-07 | 9.64e-07 | 4.04e-07 | 8.78e-07 | 9.65e-07 | 8.71e-07 | 8.74e-07 | 9.68e-07 | 9.27e-07 | 1.00e-06 | 1.02e-06 | 4.07e-07 | 8.59e-07 | +| itertools with join | 9.90e-07 | 9.82e-07 | 1.05e-06 | 1.03e-06 | 1.07e-06 | 9.85e-07 | 1.06e-06 | 9.83e-07 | 9.82e-07 | 1.04e-06 | 1.11e-06 | 1.07e-06 | 1.07e-06 | 1.04e-06 | 4.07e-07 | +| functools reduce | 1.42e-06 | 1.56e-06 | 1.45e-06 | 1.43e-06 | 1.50e-06 | 1.41e-06 | 1.46e-06 | 1.44e-06 | 1.41e-06 | 1.47e-06 | 1.49e-06 | 1.52e-06 | 1.49e-06 | 1.51e-06 | 1.43e-06 | Keep in mind that all these approaches are _very fast_, and that benchmarking at this granularity can be unstable. That caveat in mind, the two `if-statement` based approaches benchmark fastest, and the approach using `functools.reduce()` was the slowest. -The 'recommended' approach came in 4th, though it can be argued that the slowdown is justified by the increased readability and maintainability. +The 'dictionary with join' approach came in 5th, with the 'recommended dictionary' approach in 6th, though it can be argued that the slowdown for either dictionary approach is justified by the increased readability and maintainability. Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. Tests used `timeit.Timer.autorange()`, repeated 3 times. From 2e5051aab0b64681ee8c53356625fb1cf4452871 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 6 Feb 2024 11:34:53 -0800 Subject: [PATCH 609/826] [Raindrops]: Update content.md Benchmark URL (#3624) Updated `Benchmark.py` link to point at GitHub Repo rather than the website (_which returns a 404_) --- exercises/practice/raindrops/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/raindrops/.articles/performance/content.md b/exercises/practice/raindrops/.articles/performance/content.md index 7894d2f94c..82ade02c90 100644 --- a/exercises/practice/raindrops/.articles/performance/content.md +++ b/exercises/practice/raindrops/.articles/performance/content.md @@ -54,7 +54,7 @@ The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nk [approach-sequence-with-join]: https://exercism.org/tracks/python/exercises/raindrops/approaches/sequence-with-join [approach-truthy-and-falsey-with-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/truthy-and-falsey-with-fstring [approaches-intro]: https://exercism.org/tracks/python/exercises/raindrops/approaches/introduction.md -[benchmark-application]: https://exercism.org/tracks/python/exercises/raindrops/.articles/code/Benchmark.py +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.articles/performance/code/Benchmark.py [note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ [numpy]: https://numpy.org/ [pandas]: https://pandas.pydata.org/ From 67b37573e72304c3ff53d570463d710372a017af Mon Sep 17 00:00:00 2001 From: Zak <141658747+zakweb3@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:49:03 +0100 Subject: [PATCH 610/826] Fixed small typo in introduction.md (#3621) * Update introduction.md Fixed small typo in line 65 replaced : with = * Update about.md Replaced : with = in line 101 * Apply suggestions from code review * Update concepts/tuples/about.md --------- Co-authored-by: BethanyG --- concepts/tuples/about.md | 6 +++--- .../concept/tisbury-treasure-hunt/.docs/introduction.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/concepts/tuples/about.md b/concepts/tuples/about.md index bc0e81462e..3658ac1e39 100644 --- a/concepts/tuples/about.md +++ b/concepts/tuples/about.md @@ -98,7 +98,7 @@ Other data structures can be included as `tuple` elements, including other `tupl >>> nested_data_structures = ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) ->>> nested_data_structures_1 : (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) +>>> nested_data_structures_1 = (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) ``` @@ -225,5 +225,5 @@ Additionally, users can adapt a [`dataclass`][dataclass] to provide similar name [mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types [namedtuple]: https://docs.python.org/3/library/collections.html#collections.namedtuple [sequence]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range -[set]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/set.md -[tuple]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/tuple.md \ No newline at end of file +[set]: https://docs.python.org/3/library/stdtypes.html#set +[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple diff --git a/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md b/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md index 1bfbe3e615..e48857f20a 100644 --- a/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md +++ b/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md @@ -62,7 +62,7 @@ Nested data structures can be included as `tuple` elements, including other `tup >>> nested_data_structures = ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) ->>> nested_data_structures_1 : (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) +>>> nested_data_structures_1 = (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) ``` From 63cbe2bbfc0dde6f7804ba3917bea004199c4608 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 6 Feb 2024 12:40:08 -0800 Subject: [PATCH 611/826] [Tuples]: Fix Bad Links in About.md (#3625) Multiple links were pointed at the deprecated V3 repo instead of the Python docs. --- concepts/tuples/about.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/tuples/about.md b/concepts/tuples/about.md index 3658ac1e39..ea8179e2d8 100644 --- a/concepts/tuples/about.md +++ b/concepts/tuples/about.md @@ -219,9 +219,9 @@ Additionally, users can adapt a [`dataclass`][dataclass] to provide similar name [common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations [dataclass pros and cons]: https://stackoverflow.com/questions/51671699/data-classes-vs-typing-namedtuple-primary-use-cases [dataclass]: https://docs.python.org/3/library/dataclasses.html -[dict]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/dict.md +[dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [hashability]: https://docs.python.org/3/glossary.html#hashable -[list]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/list.md +[list]: https://docs.python.org/3/library/stdtypes.html#list [mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types [namedtuple]: https://docs.python.org/3/library/collections.html#collections.namedtuple [sequence]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range From 5dd5af161c09a7ac64ed342d5b6093ed204c0c16 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 11 Feb 2024 16:55:09 -0800 Subject: [PATCH 612/826] Update README.md (#3629) Add in missing 'in'. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99357745f0..d8ec664458 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Please read this [community blog post][freeing-maintainers] for details. Here to suggest a new feature or new exercise?? **Hooray!**  🎉   We'd love if you did that via our [Exercism Community Forum](https://forum.exercism.org/). Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence]. -_Thoughtful suggestions will likely result faster & more enthusiastic responses from volunteers._ +_Thoughtful suggestions will likely result in faster & more enthusiastic responses from volunteers._
From 7e3a633b180fba589fdcb0965953ba43b31726cc Mon Sep 17 00:00:00 2001 From: colinleach Date: Sun, 11 Feb 2024 23:32:46 -0600 Subject: [PATCH 613/826] [Sieve]: Draft approaches (#3626) * [Sieve]: Draft approaches * fixes various typos and random gibberish * Update introduction.md * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/nested-loops/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/snippet.txt Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/nested-loops/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/nested-loops/snippet.txt Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Does this add a spurious extra space after the link? Co-authored-by: BethanyG * Removed graph from content.md To save us forgetting it later. * Delete timeit_bar_plot.svg I didn't intend to commit this in the first place. * removed space from content.md * Update exercises/practice/sieve/.approaches/nested-loops/content.md * Update exercises/practice/sieve/.approaches/nested-loops/content.md * Update exercises/practice/sieve/.approaches/introduction.md * Update exercises/practice/sieve/.approaches/introduction.md * Update exercises/practice/sieve/.approaches/introduction.md * Code Block Corrections Somehow, the closing of the codeblocks got dropped. Added them back in, along with final typo corrections. --------- Co-authored-by: BethanyG --- .../.approaches/comprehensions/content.md | 36 + .../.approaches/comprehensions/snippet.txt | 3 + .../practice/sieve/.approaches/config.json | 40 + .../sieve/.approaches/introduction.md | 99 ++ .../sieve/.approaches/nested-loops/content.md | 49 + .../.approaches/nested-loops/snippet.txt | 8 + .../.approaches/set-operations/content.md | 69 + .../.approaches/set-operations/snippet.txt | 8 + .../practice/sieve/.articles/config.json | 14 + .../.articles/performance/code/Benchmark.py | 126 ++ .../performance/code/create_plots.py | 43 + .../performance/code/fit_gradients.py | 56 + .../performance/code/run_times.feather | Bin 0 -> 5946 bytes .../performance/code/transposed_logs.feather | Bin 0 -> 5090 bytes .../sieve/.articles/performance/content.md | 59 + .../sieve/.articles/performance/slopes.svg | 1555 +++++++++++++++++ .../sieve/.articles/performance/snippet.md | 8 + 17 files changed, 2173 insertions(+) create mode 100644 exercises/practice/sieve/.approaches/comprehensions/content.md create mode 100644 exercises/practice/sieve/.approaches/comprehensions/snippet.txt create mode 100644 exercises/practice/sieve/.approaches/config.json create mode 100644 exercises/practice/sieve/.approaches/introduction.md create mode 100644 exercises/practice/sieve/.approaches/nested-loops/content.md create mode 100644 exercises/practice/sieve/.approaches/nested-loops/snippet.txt create mode 100644 exercises/practice/sieve/.approaches/set-operations/content.md create mode 100644 exercises/practice/sieve/.approaches/set-operations/snippet.txt create mode 100644 exercises/practice/sieve/.articles/config.json create mode 100644 exercises/practice/sieve/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/sieve/.articles/performance/code/create_plots.py create mode 100644 exercises/practice/sieve/.articles/performance/code/fit_gradients.py create mode 100644 exercises/practice/sieve/.articles/performance/code/run_times.feather create mode 100644 exercises/practice/sieve/.articles/performance/code/transposed_logs.feather create mode 100644 exercises/practice/sieve/.articles/performance/content.md create mode 100644 exercises/practice/sieve/.articles/performance/slopes.svg create mode 100644 exercises/practice/sieve/.articles/performance/snippet.md diff --git a/exercises/practice/sieve/.approaches/comprehensions/content.md b/exercises/practice/sieve/.approaches/comprehensions/content.md new file mode 100644 index 0000000000..664ea32c11 --- /dev/null +++ b/exercises/practice/sieve/.approaches/comprehensions/content.md @@ -0,0 +1,36 @@ +# Comprehensions + +```python +def primes(number): + prime = (item for item in range(2, number+1) + if item not in (not_prime for item in range(2, number+1) + for not_prime in range(item*item, number+1, item))) + return list(prime) +``` + +Many of the solutions to Sieve use `comprehensions` or `generator-expressions` at some point, but this page is about examples that put almost *everything* into a single, elaborate `generator-expression` or `comprehension`. + +The above example uses a `generator-expression` to do all the calculation. + +There are at least two problems with this: +- Readability is poor. +- Performance is exceptionally bad, making this the slowest solution tested, for all input sizes. + +Notice the many `for` clauses in the generator. + +This makes the code similar to [nested loops][nested-loops], and run time scales quadratically with the size of `number`. +In fact, when this code is compiled, it _compiles to nested loops_ that have the additional overhead of generator setup and tracking. + +```python +def primes(limit): + return [number for number in range(2, limit + 1) + if all(number % divisor != 0 for divisor in range(2, number))] +``` + +This second example using a `list-comprehension` with `all()` is certainly concise and _relatively_ readable, but the performance is again quite poor. + +This is not quite a fully nested loop (_there is a short-circuit when `all()` evaluates to `False`_), but it is by no means "performant". +In this case, scaling with input size is intermediate between linear and quadratic, so not quite as bad as the first example. + + +[nested-loops]: https://exercism.org/tracks/python/exercises/sieve/approaches/nested-loops diff --git a/exercises/practice/sieve/.approaches/comprehensions/snippet.txt b/exercises/practice/sieve/.approaches/comprehensions/snippet.txt new file mode 100644 index 0000000000..125d1fb838 --- /dev/null +++ b/exercises/practice/sieve/.approaches/comprehensions/snippet.txt @@ -0,0 +1,3 @@ +def primes(limit): + return [number for number in range(2, limit + 1) if + all(number % divisor != 0 for divisor in range(2, number))] diff --git a/exercises/practice/sieve/.approaches/config.json b/exercises/practice/sieve/.approaches/config.json new file mode 100644 index 0000000000..f67a4bb244 --- /dev/null +++ b/exercises/practice/sieve/.approaches/config.json @@ -0,0 +1,40 @@ +{ + "introduction": { + "authors": [ + "colinleach", + "BethanyG" + ] + }, + "approaches": [ + { + "uuid": "85752386-a3e0-4ba5-aca7-22f5909c8cb1", + "slug": "nested-loops", + "title": "Nested Loops", + "blurb": "Relativevly clear solutions with explicit loops.", + "authors": [ + "colinleach", + "BethanyG" + ] + }, + { + "uuid": "04701848-31bf-4799-8093-5d3542372a2d", + "slug": "set-operations", + "title": "Set Operations", + "blurb": "Performance enhancements with Python sets.", + "authors": [ + "colinleach", + "BethanyG" + ] + }, + { + "uuid": "183c47e3-79b4-4afb-8dc4-0deaf094ce5b", + "slug": "comprehensions", + "title": "Comprehensions", + "blurb": "Ultra-concise code and its downsides.", + "authors": [ + "colinleach", + "BethanyG" + ] + } + ] +} diff --git a/exercises/practice/sieve/.approaches/introduction.md b/exercises/practice/sieve/.approaches/introduction.md new file mode 100644 index 0000000000..8a064c2d7b --- /dev/null +++ b/exercises/practice/sieve/.approaches/introduction.md @@ -0,0 +1,99 @@ +# Introduction + +The key to this exercise is to keep track of: +- A list of numbers. +- Their status of possibly being prime. + + +## General Guidance + +To solve this exercise, it is necessary to choose one or more appropriate data structures to store numbers and status, then decide the best way to scan through them. + +There are many ways to implement the code, and the three broad approaches listed below are not sharply separated. + + +## Approach: Using nested loops + +```python +def primes(number): + not_prime = [] + prime = [] + + for item in range(2, number+1): + if item not in not_prime: + prime.append(item) + for element in range(item*item, number+1, item): + not_prime.append(element) + + return prime +``` + +The theme here is nested, explicit `for` loops to move through ranges, testing validity as we go. + +For details and another example see [`nested-loops`][approaches-nested]. + + +## Approach: Using set operations + +```python +def primes(number): + not_prime = set() + primes = [] + + for num in range(2, number+1): + if num not in not_prime: + primes.append(num) + not_prime.update(range (num*num, number+1, num)) + + return primes +``` + +In this group, the code uses the special features of the Python [`set`][sets] to improve efficiency. + +For details and other examples see [`set-operations`][approaches-sets]. + + +## Approach: Using complex or nested comprehensions + + +```python +def primes(limit): + return [number for number in range(2, limit + 1) if + all(number % divisor != 0 for divisor in range(2, number))] +``` + +Here, the emphasis is on implementing a solution in the minimum number of lines, even at the expense of readability or performance. + +For details and another example see [`comprehensions`][approaches-comps]. + + +## Using packages outside base Python + + +In statically typed languages, common approaches include bit arrays and arrays of booleans. + +Neither of these is a natural fit for core Python, but there are external packages that could perhaps provide a better implementation: + +- For bit arrays, there is the [`bitarray`][bitarray] package and [`bitstring.BitArray()`][bitstring]. +- For arrays of booleans, we could use the NumPy package: `np.ones((number,), dtype=np.bool_)` will create a pre-dimensioned array of `True`. + +It should be stressed that these will not work in the Exercism test runner, and are mentioned here only for completeness. + +## Which Approach to Use? + + +This exercise is for learning, and is not directly relevant to production code. + +The point is to find a solution which is correct, readable, and remains reasonably fast for larger input values. + +The "set operations" example above is clean, readable, and in benchmarking was the fastest code tested. + +Further details of performance testing are given in the [Performance article][article-performance]. + +[approaches-nested]: https://exercism.org/tracks/python/exercises/sieve/approaches/nested-loops +[approaches-sets]: https://exercism.org/tracks/python/exercises/sieve/approaches/set-operations +[approaches-comps]: https://exercism.org/tracks/python/exercises/sieve/approaches/comprehensions +[article-performance]:https://exercism.org/tracks/python/exercises/sieve/articles/performance +[sets]: https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset +[bitarray]: https://pypi.org/project/bitarray/ +[bitstring]: https://bitstring.readthedocs.io/en/latest/ diff --git a/exercises/practice/sieve/.approaches/nested-loops/content.md b/exercises/practice/sieve/.approaches/nested-loops/content.md new file mode 100644 index 0000000000..75a733a27e --- /dev/null +++ b/exercises/practice/sieve/.approaches/nested-loops/content.md @@ -0,0 +1,49 @@ +# Nested Loops + + +```python +def primes(number): + not_prime = [] + prime = [] + + for item in range(2, number+1): + if item not in not_prime: + prime.append(item) + for element in range (item*item, number+1, item): + not_prime.append(element) + + return prime +``` + +This is the type of code that many people might write as a first attempt. + +It is very readable and passes the tests. + +The clear disadvantage is that run time is quadratic in the input size: `O(n**2)`, so this approach scales poorly to large input values. + +Part of the problem is the line `if item not in not_prime`, where `not-prime` is a list that may be long and unsorted. + +This operation requires searching the entire list, so run time is linear in list length: not ideal within a loop repeated many times. + +```python +def primes(number): + number += 1 + prime = [True for item in range(number)] + for index in range(2, number): + if not prime[index]: + continue + for candidate in range(2 * index, number, index): + prime[candidate] = False + return [index for index, value in enumerate(prime) if index > 1 and value] +``` + + +At first sight, this second example looks quite similar to the first. + +However, on testing it performs much better, scaling linearly with `number` rather than quadratically. + +A key difference is that list entries are tested by index: `if not prime[index]`. + +This is a constant-time operation independent of the list length. + +Relatively few programmers would have predicted such a major difference just by looking at the code, so if performance matters we should always test, not guess. diff --git a/exercises/practice/sieve/.approaches/nested-loops/snippet.txt b/exercises/practice/sieve/.approaches/nested-loops/snippet.txt new file mode 100644 index 0000000000..f765ece129 --- /dev/null +++ b/exercises/practice/sieve/.approaches/nested-loops/snippet.txt @@ -0,0 +1,8 @@ +def primes(number): + number += 1 + prime = [True for item in range(number)] + for index in range(2, number): + if not prime[index]: continue + for candidate in range(2 * index, number, index): + prime[candidate] = False + return [index for index, value in enumerate(prime) if index > 1 and value] \ No newline at end of file diff --git a/exercises/practice/sieve/.approaches/set-operations/content.md b/exercises/practice/sieve/.approaches/set-operations/content.md new file mode 100644 index 0000000000..7af5f98722 --- /dev/null +++ b/exercises/practice/sieve/.approaches/set-operations/content.md @@ -0,0 +1,69 @@ +# Set Operations + + +```python +def primes(number): + not_prime = set() + primes = [] + + for num in range(2, number+1): + if num not in not_prime: + primes.append(num) + not_prime.update(range(num*num, number+1, num)) + + return primes +``` + + +This is the fastest method so far tested, at all input sizes. + +With only a single loop, performance scales linearly: `O(n)`. + +A key step is the set `update()`. + +Less commonly seen than `add()`, which takes single element, `update()` takes any iterator of hashable values as its parameter and efficiently adds all the elements in a single operation. + +In this case, the iterator is a range resolving to all multiples, up to the limit, of the prime we just found. + +Primes are collected in a list, in ascending order, so there is no need for a separate sort operation at the end. + + +```python +def primes(number): + numbers = set(item for item in range(2, number+1)) + + not_prime = set(not_prime for item in range(2, number+1) + for not_prime in range(item**2, number+1, item)) + + return sorted(list((numbers - not_prime))) +``` + +After a set comprehension in place of an explicit loop, the second example uses set-subtraction as a key feature in the return statement. + +The resulting set needs to be converted to a list then sorted, which adds some overhead, [scaling as O(n *log* n)][sort-performance]. + +In performance testing, this code is about 4x slower than the first example, but still scales as `O(n)`. + + +```python +def primes(number: int) -> list[int]: + start = set(range(2, number + 1)) + return sorted(start - {m for n in start for m in range(2 * n, number + 1, n)}) +``` + +The third example is quite similar to the second, just moving the comprehension into the return statement. + +Performance is very similar between examples 2 and 3 at all input values. + + +## Sets: strengths and weaknesses + +Sets offer two main benefits which can be useful in this exercise: +- Entries are guaranteed to be unique. +- Determining whether the set contains a given value is a fast, constant-time operation. + +Less positively: +- The exercise specification requires a list to be returned, which may involve a conversion. +- Sets have no guaranteed ordering, so two of the above examples incur the time penalty of sorting a list at the end. + +[sort-performance]: https://en.wikipedia.org/wiki/Timsort diff --git a/exercises/practice/sieve/.approaches/set-operations/snippet.txt b/exercises/practice/sieve/.approaches/set-operations/snippet.txt new file mode 100644 index 0000000000..70f58e046b --- /dev/null +++ b/exercises/practice/sieve/.approaches/set-operations/snippet.txt @@ -0,0 +1,8 @@ +def primes(number): + not_prime = set() + primes = [] + for num in range(2, number+1): + if num not in not_prime: + primes.append(num) + not_prime.update(range(num*num, number+1, num)) + return primes \ No newline at end of file diff --git a/exercises/practice/sieve/.articles/config.json b/exercises/practice/sieve/.articles/config.json new file mode 100644 index 0000000000..60b70c7b50 --- /dev/null +++ b/exercises/practice/sieve/.articles/config.json @@ -0,0 +1,14 @@ +{ + "articles": [ + { + "slug": "performance", + "uuid": "fdbee56a-b4db-4776-8aab-3f7788c612aa", + "title": "Performance deep dive", + "authors": [ + "BethanyG", + "colinleach" + ], + "blurb": "Results and analysis of timing tests for the various approaches." + } + ] +} diff --git a/exercises/practice/sieve/.articles/performance/code/Benchmark.py b/exercises/practice/sieve/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..7027fc0ef9 --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/code/Benchmark.py @@ -0,0 +1,126 @@ +import timeit + +import pandas as pd +import numpy as np + + +# ------------ FUNCTIONS TO TIME ------------- # + +def nested_loops_1(number): + not_prime = [] + prime = [] + + for item in range(2, number + 1): + if item not in not_prime: + prime.append(item) + for element in range(item * item, number + 1, item): + not_prime.append(element) + + return prime + + +def nested_loops_2(limit): + limit += 1 + l = [True for _ in range(limit)] + for i in range(2, limit): + if not l[i]: + continue + for j in range(2 * i, limit, i): + l[j] = False + return [i for i, v in enumerate(l) if i > 1 and v] + + +def set_ops_1(number): + numbers = set(item for item in range(2, number + 1)) + + not_prime = set(not_prime for item in range(2, number + 1) + for not_prime in range(item ** 2, number + 1, item)) + + # sorting adds .2ms, but the tests won't pass with an unsorted list + return sorted(list((numbers - not_prime))) + + +def set_ops_2(number): + # fastest + not_prime = set() + primes = [] + + for num in range(2, number + 1): + if num not in not_prime: + primes.append(num) + not_prime.update(range(num * num, number + 1, num)) + + return primes + + +def set_ops_3(limit: int) -> list[int]: + start = set(range(2, limit + 1)) + return sorted(start - {m for n in start for m in range(2 * n, limit + 1, n)}) + + +def generator_comprehension(number): + # slowest + primes = (item for item in range(2, number + 1) if item not in + (not_prime for item in range(2, number + 1) for + not_prime in range(item * item, number + 1, item))) + return list(primes) + + +def list_comprehension(limit): + return [x for x in range(2, limit + 1) + if all(x % y != 0 for y in range(2, x))] if limit >= 2 else [] + + +## ---------END FUNCTIONS TO BE TIMED-------------------- ## + +## -------- Timing Code Starts Here ---------------------## + + +# Input Data Setup +inputs = [10, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000] + +# #Set up columns and rows for Pandas Data Frame +col_headers = [f'Number: {number}' for number in inputs] +row_headers = ["nested_loops_1", + "nested_loops_2", + "set_ops_1", + "set_ops_2", + "set_ops_3", + "generator_comprehension", + "list_comprehension"] + +# Empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# Function List to Call When Timing +functions = [nested_loops_1, + nested_loops_2, + set_ops_1, + set_ops_2, + set_ops_3, + generator_comprehension, + list_comprehension] + +# Run timings using timeit.autorange(). Run Each Set 3 Times. +for function, title in zip(functions, row_headers): + timings = [[ + timeit.Timer(lambda: function(data), globals=globals()).autorange()[1] / + timeit.Timer(lambda: function(data), globals=globals()).autorange()[0] + for data in inputs] for rounds in range(3)] + + # Only the fastest Cycle counts. + timing_result = min(timings) + + print(f'{title}', f'Timings : {timing_result}') + + # Insert results into the dataframe + df.loc[title, 'Number: 10':'Number: 100000'] = timing_result + +# Save the data to avoid constantly regenerating it +df.to_feather('run_times.feather') +print("\nDataframe saved to './run_times.feather'") +# +# The next bit is useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".2e")) diff --git a/exercises/practice/sieve/.articles/performance/code/create_plots.py b/exercises/practice/sieve/.articles/performance/code/create_plots.py new file mode 100644 index 0000000000..43f256ab28 --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/code/create_plots.py @@ -0,0 +1,43 @@ +import matplotlib as mpl +import matplotlib.pyplot as plt +import pandas as pd + + +# These dataframes are slow to create, so they should be saved in Feather format + +try: + df = pd.read_feather('./run_times.feather') +except FileNotFoundError: + print("File './run_times.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +try: + transposed = pd.read_feather('./transposed_logs.feather') +except FileNotFoundError: + print("File './transposed_logs.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +# Ready to start creating plots + +mpl.rcParams['axes.labelsize'] = 18 + +# bar plot of actual run times +ax = df.plot.bar(figsize=(10, 7), + logy=True, + ylabel="time (s)", + fontsize=14, + width=0.8, + rot=-30) +plt.tight_layout() +plt.savefig('../timeit_bar_plot.svg') + +# log-log plot of times vs n, to see slopes +transposed.plot(figsize=(8, 6), + marker='.', + markersize=10, + ylabel="$log_{10}(time)$ (s)", + xlabel="$log_{10}(n)$", + fontsize=14) +plt.savefig('../slopes.svg') diff --git a/exercises/practice/sieve/.articles/performance/code/fit_gradients.py b/exercises/practice/sieve/.articles/performance/code/fit_gradients.py new file mode 100644 index 0000000000..f90d8010a3 --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/code/fit_gradients.py @@ -0,0 +1,56 @@ +import pandas as pd +import numpy as np +from numpy.linalg import lstsq + + +# These dataframes are slow to create, so they should be saved in Feather format + +try: + df = pd.read_feather('./run_times.feather') +except FileNotFoundError: + print("File './run_times.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +# To plot and fit the slopes, the df needs to be log10-transformed and transposed + +inputs = [10, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000] + +pd.options.display.float_format = '{:,.2g}'.format +log_n_values = np.log10(inputs) +df[df == 0.0] = np.nan +transposed = np.log10(df).T +transposed = transposed.set_axis(log_n_values, axis=0) +transposed.to_feather('transposed_logs.feather') +print("\nDataframe saved to './transposed_logs.feather'") + +n_values = (10, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000) +log_n_values = np.log10(n_values) +row_headers = ["nested_loops_1", + "nested_loops_2", + "set_ops_1", + "set_ops_2", + "set_ops_3", + "generator_comprehension", + "list_comprehension"] + + +# Do a least-squares fit to get the slopes, working around missing values +# Apparently, it does need to be this complicated + +def find_slope(name): + log_times = transposed[name] + missing = np.isnan(log_times) + log_times = log_times[~missing] + valid_entries = len(log_times) + A = np.vstack([log_n_values[:valid_entries], np.ones(valid_entries)]).T + m, _ = lstsq(A, log_times, rcond=None)[0] + return m + + +# Print the slope results +slopes = [(name, find_slope(name)) for name in row_headers] +print('\nSlopes of log-log plots:') +for name, slope in slopes: + print(f'{name:>14} : {slope:.2f}') + diff --git a/exercises/practice/sieve/.articles/performance/code/run_times.feather b/exercises/practice/sieve/.articles/performance/code/run_times.feather new file mode 100644 index 0000000000000000000000000000000000000000..5663a046ab52d4764746ca2585505deeec888dc8 GIT binary patch literal 5946 zcmeHLeQZ-z6u)-em|GbG$DBb&^MbGhR$E$O4EA2Z3>JcXEQrYF-a7iWm9($5A9H1y z&LI)wM+g{*3C6~uf2b&e2u7G}1hLyNd?TNWyP` zL9jm=o`^vhDi{k)h|3~cEgnLyBob1`*hH&P%dd3WeGbvXv^7e zeu|a}S%e^g8rCtQ#UX%;=(F3wqS7YVt^B~g5Et+DyU>vFiMEwatH6M@=ySOIVZd3j zN?7T|euv<-SS?-)nAb6a2G`-Ly z=N2qpr<*21yRE`)ar-e3y8ITm+xa49s~W-Wu{lNTHB8m%bp{6VlFY6Er|Jwk6H^bl zAI}f9ije(qNW2NM97GFp8Tz+_)Pk5n@EkGS?dyZnBoWVz9H|dpWHiVp~on$}_kVHn%${<~;A42Oe0es}~Dg6y}MoH8U>jpwpv@Tf=`zM3ySZ8EOm5GK% zCJAbS^Q}&h4B(}Ko^)WqdIZLz&;}|P<4Dwzi^(~ITWL?Gs5xA_& zcy9zQ>oRVJF)Ev^%lP^TT-Ig$Yy>XrGOmX;R5n?c@rDRo)@A%i1TO2-rJ%{u`j;w5 z@NcVJZ-oA9dY&|1HS=^{eT<+$m?%l^vByrKkw`PCa3m>LV z6A zZ$*saYH3&cmtxIc4BJ5A$!+<_Z8y?ftT#{ITr&n;`KpCb)FEoeJuf_T;6 z2+h}`$7`tSxLGjGY&04tbrtz>KJ_QZZRQG6I_IsJ?BgmcE3aj&+XDV?jH=BPAzsxQ z{X-Ms4NXg*kY7Xl8Po>s!J`mU;Nu`V5d61Y2vQD0Te{0c$s76p%2u<~}N)8*j} zX=KS%kKpAWCh7-C;uvycq_Ij61vi!qaGi6w+(NY=VtHVx=4DAjc<7NPrT^y;g2E_2uetC#1ITo$a$z?Q7i;gpo!5)U=M31wJ vK_78M@V2cO@CIXX)!cfwKCPWYjqJR(`2mvQVUBC%WVV?Wi2d`@@{85a5>?ZfU zd(Q87?>YD0zH{$s!OWS@%+eFmhh=sQAq=5q5Q!&Bq9C!@!V(dESFb`b+NYnYplyV) zQhh8wDHbcXy<;h{Z8r&KlM`j+FJ&!)nP0+H+N{-8f|H%7D`Pp%tF!Voyp_x0ICkDx z9qVOyAzmadWd&0ek6nans}&jZExgst`Rly4IG5W_iEOoCskE7S7TH2|mEC<8uvIML zD_vAy#k)*qlgotkr7SNX-6AZYeM?IU$34l`)!lQ(-wRgYoi5(YS#37Elhf0&{5%SR z_w#PP)n;-{7$4}J43N1;r(HJ|z4T4l-$3qt91!s^JK*G9T!_j0K@Z7nkSR-MALeZH z0#{lxD?(wO?E+rl9VVB}fgeqk-N7&91*gR(1Um6A#4x*LwK!e(^Cmbayp9A51VO?H zewksgE5-o_Pj^C$6;?}y!{l(&P+@nQ91dG8-I5yKA?^-p^|{$O*?JZ~S;@Q%%+1zk zk7w&JJc{vhBL>mgSTnG8qwX`TFJd)fr7?EZoxVPdv{5uZXZUpZA{wj#@e_5+M1Cas zGDN>bzA^Uy(=z$>F%S0~qftFOPA%17%4`1I&vcAKRFZGSX_T3K!+oMHZB(XjK zKNtDW`}i#nz{ewvH2C;OWqcj#C4b_v?H9w*$bMfRhG=nLlq5zx*%;sX=UspKdIXF? z4P!srm!5ruM2b%wwyz8KRx3*Myi7x!p0Fy>0*Q(ybggcA=^{&o8V0?UIGNa=a_AUz zEu|8T96BfO*)3`P-<(e@_g521mT>5YOR|LP&C)7i9Vn*x~5S56%2{mV0=xb4;f_KK%q@vOh6WSul@J=m#^eY3|P zjvQqC(c>!DXXK~B+gqleKYS?#th-OQzMqy1XPTkm+T%&kvHGbK6;~Np->~@S9bIY| zzgyFs-Vp~Q^S86AOA1)AGQE-683CcjZ#k8JEdTXX=y|&4#yV9B%yZ{UQsAQfCGA%ewXmw_;_nUViD0Z9Gp8Xv z0kVWgatn?Rf?>y!K2a)_F!$V{J+HPzK&ipeG4{t>A^qRrso1%KONJd=N6x!km89LiR1zepo)Ku2m>lr%t(YT~%uXz4!E1hh*aN5vN)}C{Cc#mOJPG0$B zb(AsG`h0Pyb;IWMDbUt*F5&x;Ha`wrwBV&>z2TuT~zAm@;0 v-E4@vAiW8Pc>@A(tAQC%a87s~8OS*Xa*lgBggmGDKjj?tc&-+R|Ly$)+iG3y literal 0 HcmV?d00001 diff --git a/exercises/practice/sieve/.articles/performance/content.md b/exercises/practice/sieve/.articles/performance/content.md new file mode 100644 index 0000000000..9a8495b7a0 --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/content.md @@ -0,0 +1,59 @@ +# Performance + +The [Approaches page][approaches-page] discusses various ways to approach this exercise, with substantially different performance. + +## Measured timings + +The 7 code implementations described in the various approaches were [benchmarked][benchmark-code], using appropriate values for the upper limit of `n` and number of runs to average over, to keep the total testing time reasonable. + +Numerical results are tabulated below, for 9 values of the upper search limit (chosen to be about equally spaced on a log scale). + +| | 10 | 30 | 100 | 300 | 1000 | 3000 | 10,000 | 30,000 | 100,000 | +|:------------------------|---------:|---------:|----------:|----------:|-----------:|-----------:|----------:|------------:|----------:| +| nested quadratic | 4.64e-07 | 2.19e-06 | 1.92e-05 | 1.68e-04 | 1.96e-03 | 1.78e-02 | 2.03e-01 | 1.92e+00 | 2.22e+01 | +| nested linear | 8.72e-07 | 1.89e-06 | 5.32e-06 | 1.60e-05 | 5.90e-05 | 1.83e-04 | 6.09e-04 | 1.84e-03 | 6.17e-03 | +| set with update | 1.30e-06 | 3.07e-06 | 9.47e-06 | 2.96e-05 | 1.18e-04 | 3.92e-04 | 1.47e-03 | 5.15e-03 | 2.26e-02 | +| set with sort 1 | 4.97e-07 | 1.23e-06 | 3.25e-06 | 9.57e-06 | 3.72e-05 | 1.19e-04 | 4.15e-04 | 1.38e-03 | 5.17e-03 | +| set with sort 2 | 9.60e-07 | 2.61e-06 | 8.76e-06 | 2.92e-05 | 1.28e-04 | 4.46e-04 | 1.77e-03 | 6.29e-03 | 2.79e-02 | +| generator comprehension | 4.54e-06 | 2.70e-05 | 2.23e-04 | 1.91e-03 | 2.17e-02 | 2.01e-01 | 2.28e+00 | 2.09e+01 | 2.41e+02 | +| list comprehension | 2.23e-06 | 8.94e-06 | 4.36e-05 | 2.35e-04 | 1.86e-03 | 1.42e-02 | 1.39e-01 | 1.11e+00 | 1.10e+01 | + +For the smallest input, all times are fairly close to a microsecond, with about a 10-fold difference between fastest and slowest. + +In contrast, for searches up to 100,000 the timings varied by almost 5 orders of magnitude. + +This is a difference between milliseconds and minutes, which is very hard to ignore. + +## Testing algorithmic complexity + +We have discussed these solutions as `quadratic` or `linear`. +Do the experimental data support this? + +For a [power law][power-law] relationship, we have a run time `t` given by `t = a * n**x`, where `a` is a proportionality constant and `x` is the power. + +Taking logs of both sides, `log(t) = x * log(n) + constant.` + +Plots of `log(t)` against `log(n)` will be a straight line with slope equal to the power `x`. + +Graphs of the data (not included here) show that these are all straight lines for larger values of `n`, as we expected. + +Linear least-squares fits to each line gave these slope values: + +| Method | Slope | +|:-----------------|:-----:| +| nested quadratic | 1.95 | +| nested linear | 0.98 | +| set with update | 1.07 | +| set with sort 1 | 1.02 | +| set with sort 2 | 1.13 | +| generator comprehension | 1.95 | +| list comprehension | 1.69 | + +Clearly, most approaches have a slope of approximately 1 (linear) or 2 (quadratic). + +The `list-comprehension` approach is an oddity, intermediate between these extremes. + + +[approaches-page]: https://exercism.org/tracks/python/exercises/sieve/approaches +[benchmark-code]: https://github.com/exercism/python/blob/main/exercises/practice/sieve/.articles/performance/code/Benchmark.py +[power-law]: https://en.wikipedia.org/wiki/Power_law diff --git a/exercises/practice/sieve/.articles/performance/slopes.svg b/exercises/practice/sieve/.articles/performance/slopes.svg new file mode 100644 index 0000000000..447d4e5083 --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/slopes.svg @@ -0,0 +1,1555 @@ + + + + + + + + 2024-02-06T14:47:33.159377 + image/svg+xml + + + Matplotlib v3.8.0, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/practice/sieve/.articles/performance/snippet.md b/exercises/practice/sieve/.articles/performance/snippet.md new file mode 100644 index 0000000000..d741c4e5fa --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/snippet.md @@ -0,0 +1,8 @@ +def primes(number): + not_prime = set() + primes = [] + for num in range(2, number+1): + if num not in not_prime: + primes.append(num) + not_prime.update(range (num*num, number+1, num)) + return primes From 5a4dc19878235e2beef0d069b9d0a8c7eb6bfa3e Mon Sep 17 00:00:00 2001 From: Knute Snortum Date: Mon, 12 Feb 2024 09:59:39 -0800 Subject: [PATCH 614/826] Clarify that in Python there is a week descriptor called 'fifth' (#3630) * Clarify that in Python there is a week descriptor called 'fifth' * Update exercises/practice/meetup/.docs/instructions.append.md Co-authored-by: BethanyG --------- Co-authored-by: BethanyG --- exercises/practice/meetup/.docs/instructions.append.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/exercises/practice/meetup/.docs/instructions.append.md b/exercises/practice/meetup/.docs/instructions.append.md index 67d3957bc7..b3fdd74741 100644 --- a/exercises/practice/meetup/.docs/instructions.append.md +++ b/exercises/practice/meetup/.docs/instructions.append.md @@ -1,5 +1,10 @@ # Instructions append +## How this Exercise is Structured in Python + +We have added an additional week descriptor (`fifth`) for the fifth weekday of the month, if there is one. +If there is not a fifth weekday in a month, you should raise an exception. + ## Customizing and Raising Exceptions Sometimes it is necessary to both [customize](https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions) and [`raise`](https://docs.python.org/3/tutorial/errors.html#raising-exceptions) exceptions in your code. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. From 0f8d02049d4c51c48980f9acf33d6d554b469bcc Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 20 Feb 2024 02:21:03 -0800 Subject: [PATCH 615/826] Update introduction.md (#3635) Corrected Real Python link and line 21 reference to palette instead of palette_I. --- .../concept/mecha-munch-management/.docs/introduction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/introduction.md b/exercises/concept/mecha-munch-management/.docs/introduction.md index f1c5744e69..983b905c27 100644 --- a/exercises/concept/mecha-munch-management/.docs/introduction.md +++ b/exercises/concept/mecha-munch-management/.docs/introduction.md @@ -18,7 +18,7 @@ If the key is **not** found, it will _insert_ the (`key`, `default value`) pair # Looking for the value associated with key "Rock Brown". # The key does not exist, so it is added with the default value, and the value is returned. ->>> palette.setdefault('Rock Brown', '#694605') +>>> palette_I.setdefault('Rock Brown', '#694605') '#694605' # The (key, default value) pair has now been added to the dictionary. @@ -217,5 +217,5 @@ For a detailed explanation of dictionaries and methods for working with them, th [dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [fi-dict-guide]: https://blog.finxter.com/python-dictionary [fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys -[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp +[how-to-dicts]: https://realpython.com/python-dicts/ [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict From f1a064be5abe8e25e9ab10626b030b7cc26cd545 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 20 Feb 2024 02:37:54 -0800 Subject: [PATCH 616/826] Update loops.py (#3636) Correct ascending to descending in stub file. --- exercises/concept/making-the-grade/loops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/making-the-grade/loops.py b/exercises/concept/making-the-grade/loops.py index f1071b23b5..ecf7d06774 100644 --- a/exercises/concept/making-the-grade/loops.py +++ b/exercises/concept/making-the-grade/loops.py @@ -50,7 +50,7 @@ def letter_grades(highest): def student_ranking(student_scores, student_names): - """Organize the student's rank, name, and grade information in ascending order. + """Organize the student's rank, name, and grade information in descending order. :param student_scores: list - of scores in descending order. :param student_names: list - of string names by exam score in descending order. From 3187fb4e16753c0ca2ddf5b1a3492fc42109a4aa Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 27 Feb 2024 22:58:32 -0800 Subject: [PATCH 617/826] [Acronym] Add Approaches (#3357) * First draft of acronym approaches. * Performance directory. * Added starter content to performance to clear CI check. * Adding snippet placeholder for performance. * Updated and edited approaches for acronym. Modified snipetts and corrected links. * Finished up approaches and added benchmarks Finished up approaches and added benchmarks. Deleted unneeded Jupyter notebooks. * Update snippet.md Removed genexp so file would be 8 lines. * Update content.md Fixed benchmark table and broken link in performance article. --- .../practice/acronym/.articles/config.json | 11 ++ .../.articles/performance/code/Benchmark.py | 157 +++++++++++++++++ .../acronym/.articles/performance/content.md | 74 ++++++++ .../acronym/.articles/performance/snippet.md | 8 + .../practice/acronym/approaches/config.json | 56 ++++++ .../approaches/functools-reduce/content.md | 54 ++++++ .../approaches/functools-reduce/snippet.txt | 6 + .../generator-expression/content.md | 48 ++++++ .../generator-expression/snippet.txt | 5 + .../acronym/approaches/introduction.md | 160 ++++++++++++++++++ .../approaches/list-comprehension/content.md | 46 +++++ .../approaches/list-comprehension/snippet.txt | 4 + .../acronym/approaches/loop/content.md | 40 +++++ .../acronym/approaches/loop/snippet.txt | 8 + .../approaches/map-function/content.md | 49 ++++++ .../approaches/map-function/snippet.txt | 4 + .../acronym/approaches/regex-join/content.md | 98 +++++++++++ .../acronym/approaches/regex-join/snippet.txt | 6 + .../acronym/approaches/regex-sub/content.md | 75 ++++++++ .../acronym/approaches/regex-sub/snippet.txt | 6 + 20 files changed, 915 insertions(+) create mode 100644 exercises/practice/acronym/.articles/config.json create mode 100644 exercises/practice/acronym/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/acronym/.articles/performance/content.md create mode 100644 exercises/practice/acronym/.articles/performance/snippet.md create mode 100644 exercises/practice/acronym/approaches/config.json create mode 100644 exercises/practice/acronym/approaches/functools-reduce/content.md create mode 100644 exercises/practice/acronym/approaches/functools-reduce/snippet.txt create mode 100644 exercises/practice/acronym/approaches/generator-expression/content.md create mode 100644 exercises/practice/acronym/approaches/generator-expression/snippet.txt create mode 100644 exercises/practice/acronym/approaches/introduction.md create mode 100644 exercises/practice/acronym/approaches/list-comprehension/content.md create mode 100644 exercises/practice/acronym/approaches/list-comprehension/snippet.txt create mode 100644 exercises/practice/acronym/approaches/loop/content.md create mode 100644 exercises/practice/acronym/approaches/loop/snippet.txt create mode 100644 exercises/practice/acronym/approaches/map-function/content.md create mode 100644 exercises/practice/acronym/approaches/map-function/snippet.txt create mode 100644 exercises/practice/acronym/approaches/regex-join/content.md create mode 100644 exercises/practice/acronym/approaches/regex-join/snippet.txt create mode 100644 exercises/practice/acronym/approaches/regex-sub/content.md create mode 100644 exercises/practice/acronym/approaches/regex-sub/snippet.txt diff --git a/exercises/practice/acronym/.articles/config.json b/exercises/practice/acronym/.articles/config.json new file mode 100644 index 0000000000..ad22d1e171 --- /dev/null +++ b/exercises/practice/acronym/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "4c0e0a02-0bc0-4921-8016-20b0ae57804a", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach to forming an acronym.", + "authors": ["bethanyg, colinleach"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/acronym/.articles/performance/code/Benchmark.py b/exercises/practice/acronym/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..44ae8db7b2 --- /dev/null +++ b/exercises/practice/acronym/.articles/performance/code/Benchmark.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Script for timing Acronym Solutions. + +Creates timing table and timing graphs for +multiple approaches to the Acronym problem in Python. + +Created Feb 2024 +@authors: bethanygarcia, colinleach +""" + +import timeit +import re +from functools import reduce + +import pandas as pd +import numpy as np + + +# ------------ FUNCTIONS TO TIME ------------- # +def abbreviate_list_comprehension(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + return ''.join([word[0] for word in phrase]) + + +def abbreviate_genex(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + letters = (word[0] for word in phrase) + return ''.join(letters) + + +def abbreviate_loop(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace("_", " ").upper().split() + acronym = "" + + for word in phrase: + acronym += word[0] + + return acronym + + +def abbreviate_map(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return ''.join(map(lambda word: word[0], phrase)) + + +def abbreviate_reduce(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return reduce(lambda start, word: start + word[0], phrase, "") + + +def abbreviate_regex_join(phrase): + removed = re.findall(r"[a-zA-Z']+", phrase) + return ''.join(word[0] for word in removed).upper() + + +def abbreviate_finditer_join(to_abbreviate): + return ''.join(word[0][0] for word in + re.finditer(r"[a-zA-Z']+", to_abbreviate)).upper() + + +def abbreviate_regex_sub(to_abbreviate): + pattern = re.compile(r"(?>>** | **13** | **14** | **19** | **20** | **25** | **30** | **35** | **39** | **42** | **45** | **60** | **63** | **74** | **150** | **210** | **360** | **400** | **2940** | +|------------------------------ |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:------------: |:------------: |:------------: |:------------: |:-------------: | +| **loop** | 5.79e-07 | 4.96e-07 | 6.98e-07 | 7.41e-07 | 6.18e-07 | 7.25e-07 | 1.03e-06 | 7.33e-07 | 1.16e-06 | 8.71e-07 | 1.51e-06 | 1.65e-06 | 1.83e-06 | 2.43e-06 | 4.63e-06 | 7.76e-06 | 4.85e-06 | 5.94e-05 | +| **list comprehension** | 7.28e-07 | 6.57e-07 | 8.26e-07 | 8.62e-07 | 7.67e-07 | 8.30e-07 | 1.08e-06 | 8.68e-07 | 1.24e-06 | 4.00e-07 | 1.49e-06 | 1.55e-06 | 1.76e-06 | 2.19e-06 | 4.08e-06 | 7.21e-06 | 4.40e-06 | 5.42e-05 | +| **functools.reduce()** | 7.93e-07 | 6.65e-07 | 9.50e-07 | 2.43e-06 | 8.19e-07 | 9.56e-07 | 1.36e-06 | 4.12e-07 | 1.64e-06 | 1.21e-06 | 2.03e-06 | 2.14e-06 | 2.45e-06 | 3.15e-06 | 6.03e-06 | 1.03e-05 | 6.19e-06 | 8.10e-05 | +| **map()** | 8.05e-07 | 7.21e-07 | 9.34e-07 | 9.46e-07 | 8.32e-07 | 9.16e-07 | 1.23e-06 | 9.52e-07 | 1.44e-06 | 1.14e-06 | 1.71e-06 | 1.80e-06 | 2.00e-06 | 2.58e-06 | 4.81e-06 | 8.02e-06 | 4.95e-06 | 5.64e-05 | +| **generator expression** | 8.85e-07 | 7.90e-07 | 1.01e-06 | 1.01e-06 | 9.26e-07 | 2.49e-06 | 1.30e-06 | 1.06e-06 | 1.49e-06 | 1.19e-06 | 1.81e-06 | 1.86e-06 | 2.10e-06 | 2.67e-06 | 5.12e-06 | 8.61e-06 | 5.12e-06 | 5.81e-05 | +| **re.finditer()** | 1.05e-06 | 1.74e-06 | 2.44e-06 | 2.40e-06 | 2.09e-06 | 2.45e-06 | 3.28e-06 | 2.42e-06 | 8.15e-06 | 3.12e-06 | 5.15e-06 | 5.18e-06 | 5.94e-06 | 7.89e-06 | 1.46e-05 | 2.35e-05 | 1.48e-05 | 1.68e-04 | +| **regex with str.join()** | 1.62e-06 | 1.42e-06 | 1.85e-06 | 1.91e-06 | 1.66e-06 | 1.88e-06 | 2.61e-06 | 4.41e-06 | 3.14e-06 | 2.47e-06 | 3.92e-06 | 4.11e-06 | 4.61e-06 | 6.24e-06 | 1.13e-05 | 1.86e-05 | 1.19e-05 | 1.36e-04 | +| **re.findall() 1st letters** | 1.63e-06 | 1.57e-06 | 2.04e-06 | 2.12e-06 | 2.16e-06 | 2.50e-06 | 3.18e-06 | 2.90e-06 | 3.73e-06 | 3.41e-06 | 4.84e-06 | 5.22e-06 | 5.94e-06 | 1.00e-05 | 1.54e-05 | 2.48e-05 | 2.28e-05 | 1.95e-04 | +| **re.sub()** | 2.35e-06 | 1.10e-06 | 3.06e-06 | 2.94e-06 | 2.51e-06 | 2.92e-06 | 4.10e-06 | 2.91e-06 | 4.95e-06 | 3.80e-06 | 6.48e-06 | 6.39e-06 | 6.90e-06 | 9.29e-06 | 1.90e-05 | 2.98e-05 | 1.83e-05 | 2.03e-04 | + + +Keep in mind that all these approaches are very fast, and that benchmarking at this granularity can be unstable. + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [timeit module][timeit] docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + +[approaches]: https://exercism.org/tracks/python/exercises/acronym/dig_deeper +[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/acronym/approaches/functools-reduce +[approach-generator-expression]: https://exercism.org/tracks/python/exercises/acronym/approaches/generator-expression +[approach-list-comprehension]: https://exercism.org/tracks/python/exercises/acronym/approaches/list-comprehension +[approach-loop]: https://exercism.org/tracks/python/exercises/acronym/approaches/loop +[approach-map-function]: https://exercism.org/tracks/python/exercises/acronym/approaches/map-function +[approach-regex-join]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-join +[approach-regex-sub]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-sub +[benchmark-application]: https://github.com/exercism/python/tree/main/exercises/practice/acronym/.articles/performance/code/Benchmark.py +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[numpy]: https://numpy.org/ +[pandas]: https://pandas.pydata.org/docs/index.html +[timeit]: https://docs.python.org/3.11/library/timeit.html diff --git a/exercises/practice/acronym/.articles/performance/snippet.md b/exercises/practice/acronym/.articles/performance/snippet.md new file mode 100644 index 0000000000..00e1067fd9 --- /dev/null +++ b/exercises/practice/acronym/.articles/performance/snippet.md @@ -0,0 +1,8 @@ +| | **Len: 13** | **Len: 30** | **Len: 74** | **Len: 210** | **Len: 2940** | +|------------------------------ |:-----------: |:-----------: |:-----------: |:------------: |:-------------: | +| **loop** | 5.79e-07 | 7.25e-07 | 1.83e-06 | 4.63e-06 | 5.94e-05 | +| **list_comprehension** | 7.28e-07 | 8.30e-07 | 1.76e-06 | 4.08e-06 | 5.42e-05 | +| **functools.reduce()** | 7.93e-07 | 9.56e-07 | 2.45e-06 | 6.03e-06 | 8.10e-05 | +| **map()** | 8.05e-07 | 9.16e-07 | 2.00e-06 | 4.81e-06 | 5.64e-05 | +| **re.findall() 1st letters** | 1.63e-06 | 2.50e-06 | 5.94e-06 | 1.54e-05 | 1.95e-04 | +| **re.sub()** | 2.35e-06 | 2.92e-06 | 6.90e-06 | 1.90e-05 | 2.03e-04 | \ No newline at end of file diff --git a/exercises/practice/acronym/approaches/config.json b/exercises/practice/acronym/approaches/config.json new file mode 100644 index 0000000000..a3ed8e6d93 --- /dev/null +++ b/exercises/practice/acronym/approaches/config.json @@ -0,0 +1,56 @@ +{ + "introduction": { + "authors": ["BethanyG"] + }, + "approaches": [ + { + "uuid": "8ee6ac18-270b-4a62-80e6-5efb09139274", + "slug": "functools-reduce", + "title": "Functools Reduce", + "blurb": "Use functools.reduce() to form an acronym from text cleaned using str.replace().", + "authors": ["BethanyG"] + }, + { + "uuid": "d568ea30-b839-46ad-9c9b-73321a274325", + "slug": "generator-expression", + "title": "Generator Expression", + "blurb": "Use a generator expression with str.join() to form an acronym from text cleaned using str.replace().", + "authors": ["BethanyG"] + }, + { + "uuid": "da53b1bc-35c7-47a7-88d5-56ebb9d3658d", + "slug": "list-comprehension", + "title": "List Comprehension", + "blurb": "Use a list comprehension with str.join() to form an acronym from text cleaned using str.replace().", + "authors": ["BethanyG"] + }, + { + "uuid": "abd51d7d-3743-448d-b8f1-49f484ae6b30", + "slug": "loop", + "title": "Loop", + "blurb": "Use str.replace() to clean the input string and a loop with string concatenation to form the acronym.", + "authors": ["BethanyG"] + }, + { + "uuid": "9eee8db9-80f8-4ee4-aaaf-e55b78221283", + "slug": "map-function", + "title": "Map Built-in", + "blurb": "Use the built-in map() function to form an acronym after cleaning the input string with str.replace().", + "authors": ["BethanyG"] + }, + { + "uuid": "8f4dc8ba-fd1c-4c85-bcc3-8ef9dca34c7f", + "slug": "regex-join", + "title": "Regex join", + "blurb": "Use regex to clean the input string and form the acronym with str.join().", + "authors": ["BethanyG"] + }, + { + "uuid": "8830be43-44c3-45ab-8311-f588f60dfc5f", + "slug": "regex-sub", + "title": "Regex Sub", + "blurb": "Use re.sub() to clean the input string and create the acronym in one step.", + "authors": ["BethanyG"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/acronym/approaches/functools-reduce/content.md b/exercises/practice/acronym/approaches/functools-reduce/content.md new file mode 100644 index 0000000000..074db3fa28 --- /dev/null +++ b/exercises/practice/acronym/approaches/functools-reduce/content.md @@ -0,0 +1,54 @@ +# Scrub with `replace()` and join via `functools.reduce()` + + +```python +from functools import reduce + + +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return reduce(lambda start, word: start + word[0], phrase, "") +``` + + +- This approach begins by using [`str.replace()`][str-replace] to "scrub" (_remove_) non-letter characters such as `'`,`-`,`_`, and white space from `to_abbreviate`. +- The phrase is then upper-cased by calling [`str.upper()`][str-upper], +- Finally, the phrase is turned into a `list` of words by calling [`str.split()`][str-split]. + +The three methods above are all [chained][chaining] together, with the output of one method serving as the input to the next method in the "chain". +This works because both `replace()` and `upper()` return strings, and both `upper()` and `split()` take strings as arguments. +However, if `split()` were called first, `replace()` and `upper()` would fail, since neither method will take a `list` as input. + +~~~~exercism/note +`re.findall()` or `re.finditer()` can also be used to "scrub" `to_abbreviate`. +These two methods from the `re` module will return a `list` or a lazy `iterator` of results, respectively. +As of this writing, both of these methods benchmark slower than using `str.replace()` for scrubbing. +~~~~ + + +Once the phrase is scrubbed and turned into a word `list`, the acronym is created via `reduce()`. +`reduce()` is a method from the [`functools`][functools] module, which provides support for higher-order functions and functional programming in Python. + + +[`functools.reduce()`][reduce] applies an anonymous two-argument function (_the [lambda][python lambdas] in the code example_) to the items of an iterable. + The application of the function travels from left to right, so that the iterable becomes a single value (_it is "reduced" to a single value_). + + + Using code from the example above, `reduce(lambda start, word: start + word[0], ['GNU', 'IMAGE', 'MANIPULATION', 'PROGRAM'])` would calculate `((('GNU'[0] + 'IMAGE'[0])+'MANIPULATION'[0])+'PROGRAM'[0])`, or `GIMP`. + The left argument, `start`, is the _accumulated value_ and the right argument, `word`, is the value from the iterable that is used to update the accumulated 'total'. + The optional 'initializer' value '' is used here, and is placed ahead/before the items of the iterable in the calculation, and serves as a default if the iterable that is passed is empty. + + +Since using `reduce()` is fairly succinct, it is put directly on the `return` line to produce the acronym rather than assigning and returning an intermediate variable. + + +In benchmarks, this solution performed about as well as both the `loops` and the `list-comprehension` solutions. + +[chaining]: https://pyneng.readthedocs.io/en/latest/book/04_data_structures/method_chaining.html +[functools]: https://docs.python.org/3/library/functools.html +[reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace +[str-split]: https://docs.python.org/3/library/stdtypes.html#str.split +[str-upper]: https://docs.python.org/3/library/stdtypes.html#str.upper +[python lambdas]: https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions diff --git a/exercises/practice/acronym/approaches/functools-reduce/snippet.txt b/exercises/practice/acronym/approaches/functools-reduce/snippet.txt new file mode 100644 index 0000000000..190d5d4aef --- /dev/null +++ b/exercises/practice/acronym/approaches/functools-reduce/snippet.txt @@ -0,0 +1,6 @@ +from functools import reduce + +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return reduce(lambda start, word: start + word[0], phrase, "") \ No newline at end of file diff --git a/exercises/practice/acronym/approaches/generator-expression/content.md b/exercises/practice/acronym/approaches/generator-expression/content.md new file mode 100644 index 0000000000..f5b590ccaa --- /dev/null +++ b/exercises/practice/acronym/approaches/generator-expression/content.md @@ -0,0 +1,48 @@ +# Scrub with `replace()` and join via `generator-expression` + + +```python +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + + # note the lack of square brackets around the comprehension. + return ''.join(word[0] for word in phrase) +``` + + +- This approach begins by using [`str.replace()`][str-replace] to "scrub" (_remove_) non-letter characters such as `'`,`-`,`_`, and white space from `to_abbreviate`. +- The phrase is then upper-cased by calling [`str.upper()`][str-upper], +- Finally, the phrase is turned into a `list` of words by calling [`str.split()`][str-split]. + +The three methods above are all [chained][chaining] together, with the output of one method serving as the input to the next method in the "chain". +This works because both `replace()` and `upper()` return strings, and both `upper()` and `split()` take strings as arguments. +However, if `split()` were called first, `replace()` and `upper()` would fail, since neither method will take a `list` as input. + +~~~~exercism/note +`re.findall()` or `re.finditer()` can also be used to "scrub" `to_abbreviate`. +These two methods from the `re` module will return a `list` or a lazy `iterator` of results, respectively. +As of this writing, both of these methods benchmark slower than using `str.replace()` for scrubbing. +~~~~ + + +A [`generator-expression`][generator-expression] is then used to iterate through the phrase and select the first letters of each word via [`bracket notation`][subscript notation]. + + +Generator expressions are short-form [generators][generators] - lazy iterators that produce their values _on demand_, instead of saving them to memory. +This generator expression is consumed by [`str.join()`][str-join], which joins the generated letters together using an empty string. +Other "seperator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. +Since the generator expression and `join()` are fairly succinct, they are put directly on the `return` line rather than assigning and returning an intermediate variable for the acronym. + + +In benchmarks, this solution was surprisingly slower than the `list comprehension` version. +[This article][Oscar Alsing] from Oscar Alsing briefly explains why. + +[Oscar Alsing]: https://www.oscaralsing.com/list-comprehension-vs-generator-expression/#:~:text=List%20comprehensions%20are%20usually%20faster,difference%20is%20often%20quite%20small. +[chaining]: https://pyneng.readthedocs.io/en/latest/book/04_data_structures/method_chaining.html +[generator-expression]: https://dbader.org/blog/python-generator-expressions +[generators]: https://dbader.org/blog/python-generators +[str-join]: https://docs.python.org/3/library/stdtypes.html#str.join +[str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace +[str-split]: https://docs.python.org/3/library/stdtypes.html#str.split +[str-upper]: https://docs.python.org/3/library/stdtypes.html#str.upper +[subscript notation]: https://docs.python.org/3/glossary.html#term-slice diff --git a/exercises/practice/acronym/approaches/generator-expression/snippet.txt b/exercises/practice/acronym/approaches/generator-expression/snippet.txt new file mode 100644 index 0000000000..eb4a143df8 --- /dev/null +++ b/exercises/practice/acronym/approaches/generator-expression/snippet.txt @@ -0,0 +1,5 @@ +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + + # note the lack of square brackets around the comprehension. + return ''.join(word[0] for word in phrase) \ No newline at end of file diff --git a/exercises/practice/acronym/approaches/introduction.md b/exercises/practice/acronym/approaches/introduction.md new file mode 100644 index 0000000000..38b606b4a2 --- /dev/null +++ b/exercises/practice/acronym/approaches/introduction.md @@ -0,0 +1,160 @@ +# Introduction + +There are multiple Pythonic ways to solve the Acronym exercise. +Among them are: + +- Using `str.replace()` to scrub the input, and: + - joining with a `for loop` with string concatenation via the `+` operator. + - joining via `str.join()`, passing a `list-comprehension` or `generator-expression`. + - joining via `str.join()`, passing `map()`. + - joining via `functools.reduce()`. + +- Using `re.findall()`/`re.finditer()` to scrub the input, and: + - joining via `str.join()`, passing a `generator-expression`. + + - Using `re.sub()` for both cleaning and joining (_using "only" regex for almost everything_)` + + +## General Guidance + +The goal of the Acronym exercise is to collect the first letters of each word in the input phrase and return them as a single capitalized string (_the acronym_). +The challenge is to efficiently identify and capitalize the first letters while removing or ignoring non-letter characters such as `'`,`-`,`_`, and white space. + + +There are two idiomatic strategies for non-letter character removal: +- Python's built-in [`str.replace()`][str-replace]. +- The [`re`][re] module, (_regular expressions_). + +For all but the most complex scenarios, using `str.replace()` is generally more efficient than using a regular expression. + + +Forming the final acronym is most easily done with a direct or indirect `loop`, after splitting the input into a word list via [`str.split()`][str-split]. +The majority of these approaches demonstrate alternatives to the "classic" looping structure using various other iteration techniques. +Some `regex` methods can avoid looping altogether, although they can become very non-performant due to excessive backtracking. + +Strings are _immutable_, so any method to produce an acronym will be creating and returning a new `str`. + + +## Approach: scrub with `replace()` and join via `for` loop + +```python +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + acronym = '' + + for word in phrase: + acronym += word[0] + + return acronym +``` + +For more information, take a look at the [loop approach][approach-loop]. + + +## Approach: scrub with `replace()` and join via `list comprehension` or `Generator expression` + + +```python +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + + return ''.join([word[0] for word in phrase]) + +###OR### + +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + + # note the parenthesis instead of square brackets. + return ''.join((word[0] for word in phrase)) +``` + +For more information, check out the [list-comprehension][approach-list-comprehension] approach or the [generator-expression][approach-generator-expression] approach. + + +## Approach: scrub with `replace()` and join via `map()` + +```python +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return ''.join(map(lambda word: word[0], phrase)) +``` + +For more information, read the [map][approach-map-function] approach. + + +## Approach: scrub with `replace()` and join via `functools.reduce()` + +```python +from functools import reduce + + +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return reduce(lambda start, word: start + word[0], phrase, "") +``` + +For more information, take a look at the [functools.reduce()][approach-functools-reduce] approach. + + +## Approach: filter with `re.findall()` and join via `str.join()` + +```python +import re + + +def abbreviate(phrase): + removed = re.findall(r"[a-zA-Z']+", phrase) + + return ''.join(word[0] for word in removed).upper() +``` + +For more information, take a look at the [regex-join][approach-regex-join] approach. + + +## Approach: use `re.sub()` + +```python +import re + + +def abbreviate_regex_sub(to_abbreviate): + pattern = re.compile(r"(? Date: Tue, 27 Feb 2024 23:12:31 -0800 Subject: [PATCH 618/826] Update config.json (#3642) corrected author name to see if it changed rep awarded. --- .../practice/acronym/approaches/config.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/exercises/practice/acronym/approaches/config.json b/exercises/practice/acronym/approaches/config.json index a3ed8e6d93..b5bbca7a23 100644 --- a/exercises/practice/acronym/approaches/config.json +++ b/exercises/practice/acronym/approaches/config.json @@ -1,6 +1,6 @@ { "introduction": { - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, "approaches": [ { @@ -8,49 +8,49 @@ "slug": "functools-reduce", "title": "Functools Reduce", "blurb": "Use functools.reduce() to form an acronym from text cleaned using str.replace().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "d568ea30-b839-46ad-9c9b-73321a274325", "slug": "generator-expression", "title": "Generator Expression", "blurb": "Use a generator expression with str.join() to form an acronym from text cleaned using str.replace().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "da53b1bc-35c7-47a7-88d5-56ebb9d3658d", "slug": "list-comprehension", "title": "List Comprehension", "blurb": "Use a list comprehension with str.join() to form an acronym from text cleaned using str.replace().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "abd51d7d-3743-448d-b8f1-49f484ae6b30", "slug": "loop", "title": "Loop", "blurb": "Use str.replace() to clean the input string and a loop with string concatenation to form the acronym.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "9eee8db9-80f8-4ee4-aaaf-e55b78221283", "slug": "map-function", "title": "Map Built-in", "blurb": "Use the built-in map() function to form an acronym after cleaning the input string with str.replace().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "8f4dc8ba-fd1c-4c85-bcc3-8ef9dca34c7f", "slug": "regex-join", "title": "Regex join", "blurb": "Use regex to clean the input string and form the acronym with str.join().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "8830be43-44c3-45ab-8311-f588f60dfc5f", "slug": "regex-sub", "title": "Regex Sub", "blurb": "Use re.sub() to clean the input string and create the acronym in one step.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] } ] -} \ No newline at end of file +} From 8bf0fb2d1bea9f2db5431143c327a08f1284e361 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 27 Feb 2024 23:25:51 -0800 Subject: [PATCH 619/826] Rename approaches to .approaches (#3643) Renamed approaches directory to .approaches, so that the articles will show up on site. --- .../practice/acronym/{approaches => .approaches}/config.json | 0 .../{approaches => .approaches}/functools-reduce/content.md | 0 .../{approaches => .approaches}/functools-reduce/snippet.txt | 0 .../{approaches => .approaches}/generator-expression/content.md | 0 .../{approaches => .approaches}/generator-expression/snippet.txt | 0 .../practice/acronym/{approaches => .approaches}/introduction.md | 0 .../{approaches => .approaches}/list-comprehension/content.md | 0 .../{approaches => .approaches}/list-comprehension/snippet.txt | 0 .../practice/acronym/{approaches => .approaches}/loop/content.md | 0 .../practice/acronym/{approaches => .approaches}/loop/snippet.txt | 0 .../acronym/{approaches => .approaches}/map-function/content.md | 0 .../acronym/{approaches => .approaches}/map-function/snippet.txt | 0 .../acronym/{approaches => .approaches}/regex-join/content.md | 0 .../acronym/{approaches => .approaches}/regex-join/snippet.txt | 0 .../acronym/{approaches => .approaches}/regex-sub/content.md | 0 .../acronym/{approaches => .approaches}/regex-sub/snippet.txt | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename exercises/practice/acronym/{approaches => .approaches}/config.json (100%) rename exercises/practice/acronym/{approaches => .approaches}/functools-reduce/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/functools-reduce/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/generator-expression/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/generator-expression/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/introduction.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/list-comprehension/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/list-comprehension/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/loop/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/loop/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/map-function/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/map-function/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/regex-join/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/regex-join/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/regex-sub/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/regex-sub/snippet.txt (100%) diff --git a/exercises/practice/acronym/approaches/config.json b/exercises/practice/acronym/.approaches/config.json similarity index 100% rename from exercises/practice/acronym/approaches/config.json rename to exercises/practice/acronym/.approaches/config.json diff --git a/exercises/practice/acronym/approaches/functools-reduce/content.md b/exercises/practice/acronym/.approaches/functools-reduce/content.md similarity index 100% rename from exercises/practice/acronym/approaches/functools-reduce/content.md rename to exercises/practice/acronym/.approaches/functools-reduce/content.md diff --git a/exercises/practice/acronym/approaches/functools-reduce/snippet.txt b/exercises/practice/acronym/.approaches/functools-reduce/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/functools-reduce/snippet.txt rename to exercises/practice/acronym/.approaches/functools-reduce/snippet.txt diff --git a/exercises/practice/acronym/approaches/generator-expression/content.md b/exercises/practice/acronym/.approaches/generator-expression/content.md similarity index 100% rename from exercises/practice/acronym/approaches/generator-expression/content.md rename to exercises/practice/acronym/.approaches/generator-expression/content.md diff --git a/exercises/practice/acronym/approaches/generator-expression/snippet.txt b/exercises/practice/acronym/.approaches/generator-expression/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/generator-expression/snippet.txt rename to exercises/practice/acronym/.approaches/generator-expression/snippet.txt diff --git a/exercises/practice/acronym/approaches/introduction.md b/exercises/practice/acronym/.approaches/introduction.md similarity index 100% rename from exercises/practice/acronym/approaches/introduction.md rename to exercises/practice/acronym/.approaches/introduction.md diff --git a/exercises/practice/acronym/approaches/list-comprehension/content.md b/exercises/practice/acronym/.approaches/list-comprehension/content.md similarity index 100% rename from exercises/practice/acronym/approaches/list-comprehension/content.md rename to exercises/practice/acronym/.approaches/list-comprehension/content.md diff --git a/exercises/practice/acronym/approaches/list-comprehension/snippet.txt b/exercises/practice/acronym/.approaches/list-comprehension/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/list-comprehension/snippet.txt rename to exercises/practice/acronym/.approaches/list-comprehension/snippet.txt diff --git a/exercises/practice/acronym/approaches/loop/content.md b/exercises/practice/acronym/.approaches/loop/content.md similarity index 100% rename from exercises/practice/acronym/approaches/loop/content.md rename to exercises/practice/acronym/.approaches/loop/content.md diff --git a/exercises/practice/acronym/approaches/loop/snippet.txt b/exercises/practice/acronym/.approaches/loop/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/loop/snippet.txt rename to exercises/practice/acronym/.approaches/loop/snippet.txt diff --git a/exercises/practice/acronym/approaches/map-function/content.md b/exercises/practice/acronym/.approaches/map-function/content.md similarity index 100% rename from exercises/practice/acronym/approaches/map-function/content.md rename to exercises/practice/acronym/.approaches/map-function/content.md diff --git a/exercises/practice/acronym/approaches/map-function/snippet.txt b/exercises/practice/acronym/.approaches/map-function/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/map-function/snippet.txt rename to exercises/practice/acronym/.approaches/map-function/snippet.txt diff --git a/exercises/practice/acronym/approaches/regex-join/content.md b/exercises/practice/acronym/.approaches/regex-join/content.md similarity index 100% rename from exercises/practice/acronym/approaches/regex-join/content.md rename to exercises/practice/acronym/.approaches/regex-join/content.md diff --git a/exercises/practice/acronym/approaches/regex-join/snippet.txt b/exercises/practice/acronym/.approaches/regex-join/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/regex-join/snippet.txt rename to exercises/practice/acronym/.approaches/regex-join/snippet.txt diff --git a/exercises/practice/acronym/approaches/regex-sub/content.md b/exercises/practice/acronym/.approaches/regex-sub/content.md similarity index 100% rename from exercises/practice/acronym/approaches/regex-sub/content.md rename to exercises/practice/acronym/.approaches/regex-sub/content.md diff --git a/exercises/practice/acronym/approaches/regex-sub/snippet.txt b/exercises/practice/acronym/.approaches/regex-sub/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/regex-sub/snippet.txt rename to exercises/practice/acronym/.approaches/regex-sub/snippet.txt From e6be68abbbd6c76c12d33fd5321a77beb8a81baa Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Feb 2024 00:39:09 -0800 Subject: [PATCH 620/826] Update no-important-files-changed.yml (#3644) fix for html access for [no important files changed] --- .../workflows/no-important-files-changed.yml | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 26b068bc46..2f31421581 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -1,9 +1,16 @@ name: No important files changed on: - pull_request: + pull_request_target: types: [opened] branches: [main] + paths: + - "exercises/concept/**" + - "exercises/practice/**" + - "!exercises/*/*/.meta/config.json" + - "!exercises/*/*/.meta/tests.toml" + - "!exercises/*/*/.docs/instructions.md" + - "!exercises/*/*/.docs/introduction.md" permissions: pull-requests: write @@ -14,50 +21,40 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} + ref: ${{ github.head_ref }} - name: Check if important files changed id: check run: | set -exo pipefail - # fetch a ref to the main branch so we can diff against it git remote set-branches origin main git fetch --depth 1 origin main + git diff --diff-filter=M --name-only origin/main + for changed_file in $(git diff --diff-filter=M --name-only origin/main); do - if ! echo "$changed_file" | grep --quiet --extended-regexp 'exercises/(practice|concept)' ; then - continue - fi - slug="$(echo "$changed_file" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" - path_before_slug="$(echo "$changed_file" | sed --regexp-extended "s#(.*)/$slug/.*#\\1#" )" - path_after_slug="$( echo "$changed_file" | sed --regexp-extended "s#.*/$slug/(.*)#\\1#" )" + slug="$(echo "${changed_file}" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" + path_before_slug="$(echo "${changed_file}" | sed --regexp-extended "s#(.*)/${slug}/.*#\\1#" )" + path_after_slug="$( echo "${changed_file}" | sed --regexp-extended "s#.*/${slug}/(.*)#\\1#" )" + config_json_file="${path_before_slug}/${slug}/.meta/config.json" - if ! [ -f "$path_before_slug/$slug/.meta/config.json" ]; then + if ! [ -f "${config_json_file}" ]; then # cannot determine if important files changed without .meta/config.json continue fi - # returns 0 if the filter matches, 1 otherwise - # | contains($path_after_slug) - if jq --exit-status \ - --arg path_after_slug "$path_after_slug" \ - '[.files.test, .files.invalidator, .files.editor] | flatten | index($path_after_slug)' \ - "$path_before_slug/$slug/.meta/config.json" \ - > /dev/null; - then - echo "important_files_changed=true" >> "$GITHUB_OUTPUT" - exit 0 - fi + changed=$(jq --arg path "${path_after_slug}" '[.files.test, .files.invalidator, .files.editor] | flatten | index($path) != null' "${config_json_file}") + echo "important_files_changed=${changed}" >> "$GITHUB_OUTPUT" done - echo "important_files_changed=false" >> "$GITHUB_OUTPUT" - - name: Suggest to add [no important files changed] if: steps.check.outputs.important_files_changed == 'true' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: - github-token: ${{ github.token }} script: | const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" github.rest.issues.createComment({ From aae2c8902d07b57b1b4d19644a102ec9ebc86a01 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Feb 2024 07:51:59 -0800 Subject: [PATCH 621/826] Synced docs and metadata to problem specificiations. (#3646) --- exercises/practice/meetup/.meta/config.json | 3 +-- exercises/practice/raindrops/.meta/config.json | 2 +- exercises/practice/say/.docs/instructions.md | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/exercises/practice/meetup/.meta/config.json b/exercises/practice/meetup/.meta/config.json index 9a08a5da71..f269bc387f 100644 --- a/exercises/practice/meetup/.meta/config.json +++ b/exercises/practice/meetup/.meta/config.json @@ -29,6 +29,5 @@ ] }, "blurb": "Calculate the date of meetups.", - "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month", - "source_url": "http://www.copiousfreetime.org/" + "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month" } diff --git a/exercises/practice/raindrops/.meta/config.json b/exercises/practice/raindrops/.meta/config.json index 463a523712..70763b83e2 100644 --- a/exercises/practice/raindrops/.meta/config.json +++ b/exercises/practice/raindrops/.meta/config.json @@ -26,7 +26,7 @@ ".meta/example.py" ] }, - "blurb": "Convert a number to a string, the content of which depends on the number's factors.", + "blurb": "Convert a number into its corresponding raindrop sounds - Pling, Plang and Plong.", "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.", "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz" } diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index fb4a6dfb98..ad3d347782 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -30,8 +30,6 @@ Implement breaking a number up into chunks of thousands. So `1234567890` should yield a list like 1, 234, 567, and 890, while the far simpler `1000` should yield just 1 and 0. -The program must also report any values that are out of range. - ## Step 3 Now handle inserting the appropriate scale word between those chunks. From fd73808a2a70e0b13d798249d1ff189de909a877 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Feb 2024 07:52:15 -0800 Subject: [PATCH 622/826] [Anagram]: Updated Tests from `problem-specifications` (#3647) * Updated anagram tests from problem specs. * Removed accidental solution code from stub file. --- exercises/practice/anagram/.meta/tests.toml | 6 ++++++ exercises/practice/anagram/anagram_test.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml index 4f43811d26..4d90562705 100644 --- a/exercises/practice/anagram/.meta/tests.toml +++ b/exercises/practice/anagram/.meta/tests.toml @@ -78,3 +78,9 @@ include = false [33d3f67e-fbb9-49d3-a90e-0beb00861da7] description = "words other than themselves can be anagrams" reimplements = "a0705568-628c-4b55-9798-82e4acde51ca" + +[a6854f66-eec1-4afd-a137-62ef2870c051] +description = "handles case of greek letters" + +[fd3509e5-e3ba-409d-ac3d-a9ac84d13296] +description = "different characters may have the same bytes" diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py index bed1d80fc9..48b99ec2d3 100644 --- a/exercises/practice/anagram/anagram_test.py +++ b/exercises/practice/anagram/anagram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2023-12-27 +# File last updated on 2024-02-28 import unittest @@ -93,3 +93,13 @@ def test_words_other_than_themselves_can_be_anagrams(self): candidates = ["LISTEN", "Silent"] expected = ["Silent"] self.assertCountEqual(find_anagrams("LISTEN", candidates), expected) + + def test_handles_case_of_greek_letters(self): + candidates = ["ΒΓΑ", "ΒΓΔ", "γβα", "αβγ"] + expected = ["ΒΓΑ", "γβα"] + self.assertCountEqual(find_anagrams("ΑΒΓ", candidates), expected) + + def test_different_characters_may_have_the_same_bytes(self): + candidates = ["€a"] + expected = [] + self.assertCountEqual(find_anagrams("a⬂", candidates), expected) From 412deec1d45272fbc9d1a532902a2d83f4bd6bc7 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Feb 2024 07:53:57 -0800 Subject: [PATCH 623/826] Updated reverse string tests from problem specifications. (#3648) [no important files changed] This test case (though new) is easily passed by the canonical/vast majority solution, so student solution re-testing is not needed at this time. --- .../practice/reverse-string/.meta/tests.toml | 24 ++++++++++++++++--- .../reverse-string/reverse_string_test.py | 5 +++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/exercises/practice/reverse-string/.meta/tests.toml b/exercises/practice/reverse-string/.meta/tests.toml index 2113a53364..555b8e4f2e 100644 --- a/exercises/practice/reverse-string/.meta/tests.toml +++ b/exercises/practice/reverse-string/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [c3b7d806-dced-49ee-8543-933fd1719b1c] description = "an empty string" @@ -19,3 +26,14 @@ description = "a palindrome" [b9e7dec1-c6df-40bd-9fa3-cd7ded010c4c] description = "an even-sized word" + +[1bed0f8a-13b0-4bd3-9d59-3d0593326fa2] +description = "wide characters" + +[93d7e1b8-f60f-4f3c-9559-4056e10d2ead] +description = "grapheme cluster with pre-combined form" +include = false + +[1028b2c1-6763-4459-8540-2da47ca512d9] +description = "grapheme clusters" +include = false diff --git a/exercises/practice/reverse-string/reverse_string_test.py b/exercises/practice/reverse-string/reverse_string_test.py index 0c3298704c..8316841819 100644 --- a/exercises/practice/reverse-string/reverse_string_test.py +++ b/exercises/practice/reverse-string/reverse_string_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-02-28 import unittest @@ -27,3 +27,6 @@ def test_a_palindrome(self): def test_an_even_sized_word(self): self.assertEqual(reverse("drawer"), "reward") + + def test_wide_characters(self): + self.assertEqual(reverse("子猫"), "猫子") From ddef41a1b4e8f59aa0cac908b034d77914a5679c Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Thu, 29 Feb 2024 15:50:17 +0000 Subject: [PATCH 624/826] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3649)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/0c0972d1df4cd18d98c7df316348315b06ef49b4 --- .../workflows/no-important-files-changed.yml | 60 +++---------------- 1 file changed, 9 insertions(+), 51 deletions(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 2f31421581..b940c5991c 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -7,59 +7,17 @@ on: paths: - "exercises/concept/**" - "exercises/practice/**" - - "!exercises/*/*/.meta/config.json" - - "!exercises/*/*/.meta/tests.toml" - - "!exercises/*/*/.docs/instructions.md" - - "!exercises/*/*/.docs/introduction.md" + - "!exercises/*/*/.approaches/**" + - "!exercises/*/*/.articles/**" + - "!exercises/*/*/.docs/**" + - "!exercises/*/*/.meta/**" permissions: pull-requests: write jobs: - no_important_files_changed: - name: No important files changed - runs-on: ubuntu-22.04 - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - with: - repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} - ref: ${{ github.head_ref }} - - - name: Check if important files changed - id: check - run: | - set -exo pipefail - - git remote set-branches origin main - git fetch --depth 1 origin main - - git diff --diff-filter=M --name-only origin/main - - for changed_file in $(git diff --diff-filter=M --name-only origin/main); do - slug="$(echo "${changed_file}" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" - path_before_slug="$(echo "${changed_file}" | sed --regexp-extended "s#(.*)/${slug}/.*#\\1#" )" - path_after_slug="$( echo "${changed_file}" | sed --regexp-extended "s#.*/${slug}/(.*)#\\1#" )" - config_json_file="${path_before_slug}/${slug}/.meta/config.json" - - if ! [ -f "${config_json_file}" ]; then - # cannot determine if important files changed without .meta/config.json - continue - fi - - changed=$(jq --arg path "${path_after_slug}" '[.files.test, .files.invalidator, .files.editor] | flatten | index($path) != null' "${config_json_file}") - echo "important_files_changed=${changed}" >> "$GITHUB_OUTPUT" - done - - - name: Suggest to add [no important files changed] - if: steps.check.outputs.important_files_changed == 'true' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea - with: - script: | - const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: body - }) + pause: + uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main + with: + repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} + ref: ${{ github.head_ref }} From 052ae68d18ae102c6ed356804dff81f3ea75fecf Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 29 Feb 2024 08:12:16 -0800 Subject: [PATCH 625/826] [BlackJack]: Clarify Blackjack Hand Definition in instructions.md (#3650) Per [forum discussion](http://forum.exercism.org/t/suggested-changes-to-python-learning-exercise-instructions/9896/11). --- exercises/concept/black-jack/.docs/instructions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exercises/concept/black-jack/.docs/instructions.md b/exercises/concept/black-jack/.docs/instructions.md index 199fabc900..d6c8fc502f 100644 --- a/exercises/concept/black-jack/.docs/instructions.md +++ b/exercises/concept/black-jack/.docs/instructions.md @@ -79,7 +79,9 @@ Remember: the value of the hand with the ace needs to be as high as possible _wi ## 4. Determine a "Natural" or "Blackjack" Hand -If the first two cards a player is dealt are an ace (`A`) and a ten-card (10, `K`, `Q` or `J`), giving a score of 21 in two cards, the hand is considered a `natural` or `blackjack`. +If the first two cards a player is dealt are an ace (A) and a ten-card (_10, K , Q or J_), then the player has a score of 21. +This is known as a blackjack hand. + Define the `is_blackjack(, )` function with parameters `card_one` and `card_two`, which are a pair of cards. Determine if the two-card hand is a `blackjack`, and return the boolean `True` if it is, `False` otherwise. From 55a0ab42851acbac183b5045f86f551b375eb30b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 29 Feb 2024 10:28:31 -0800 Subject: [PATCH 626/826] Superset subset and operator typo corrections. (#3651) --- concepts/sets/about.md | 4 ++-- exercises/concept/cater-waiter/.docs/introduction.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 0b8fc842f1..321c5116c9 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -172,7 +172,7 @@ Traceback (most recent call last): Sets have methods that generally mimic [mathematical set operations][mathematical-sets]. Most (_not all_) of these methods have an [operator][operator] equivalent. -Methods generally take any `iterable` as an argument, while operators require that both things being compared are `sets` or `frozensets`. +Methods generally take any `iterable` as an argument, while operators require that both sides of the operation are `sets` or `frozensets`. ### Membership Testing Between Sets @@ -266,7 +266,7 @@ False False # A set is always a loose superset of itself. ->>> set(animals) <= set(animals) +>>> set(animals) >= set(animals) True ``` diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 235ae86937..94a0b4c766 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -114,7 +114,7 @@ TypeError: unhashable type: 'set' Sets have methods that generally mimic [mathematical set operations][mathematical-sets]. Most (_not all_) of these methods have an [operator][operator] equivalent. -Methods generally take any `iterable` as an argument, while operators require that both things being compared are `sets` or `frozensets`. +Methods generally take any `iterable` as an argument, while operators require that both sides of the operation are `sets` or `frozensets`. ### Disjoint Sets @@ -212,7 +212,7 @@ False False # A set is always a loose superset of itself. ->>> set(animals) <= set(animals) +>>> set(animals) >= set(animals) True ``` From c224d7cb439717d656b03c5e46f771cb7680f1ba Mon Sep 17 00:00:00 2001 From: izmirli Date: Sat, 2 Mar 2024 23:30:11 +0200 Subject: [PATCH 627/826] [Mecha Munch Management] Add test case for send_to_store of extra item on isle_mapping (#3653) --- .../dict_methods_test.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 8ec0211d53..1bb09b242d 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -31,7 +31,6 @@ def test_add_item(self): f'expected: {expected} once the item was added.') self.assertEqual(actual_result, expected, msg=error_msg) - @pytest.mark.task(taskno=2) def test_read_notes(self): @@ -130,12 +129,20 @@ def test_send_to_store(self): 'Yoghurt': ['Aisle 2', True], 'Milk': ['Aisle 2', True]}), ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, - 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4}, + 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4}, {'Apple': ['Aisle 1', False], 'Raspberry': ['Aisle 6', False], 'Blueberries': ['Aisle 6', False], 'Broccoli': ['Aisle 3', False], - 'Kiwi': ['Aisle 6', False], 'Melon': ['Aisle 6', False]}) - ] + 'Kiwi': ['Aisle 6', False], 'Melon': ['Aisle 6', False]}), + + ({'Orange': 1}, + {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], + 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), + + ({'Banana': 3, 'Apple': 2, 'Orange': 1}, + {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], + 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), + ] output_data = [ {'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], @@ -146,8 +153,13 @@ def test_send_to_store(self): {'Raspberry': [2, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], 'Kiwi': [1, 'Aisle 6', False], 'Broccoli': [2, 'Aisle 3', False], - 'Blueberries': [5, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False]} - ] + 'Blueberries': [5, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False]}, + + {'Orange': [1, 'Aisle 4', False]}, + + {'Orange': [1, 'Aisle 4', False], 'Banana': [3, 'Aisle 5', False], + 'Apple': [2, 'Aisle 4', False]}, + ] for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): From 8f5286fed62318380c6b5f0ae30cfdab8ee794bf Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 2 Mar 2024 14:00:58 -0800 Subject: [PATCH 628/826] [Mecha Munch Management]: Corrected Brackets in dict_methods_test.py (#3654) [no important files changed] Trivial, but it was bugging me. --- exercises/concept/mecha-munch-management/dict_methods_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 1bb09b242d..622f425d5a 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -142,7 +142,7 @@ def test_send_to_store(self): ({'Banana': 3, 'Apple': 2, 'Orange': 1}, {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), - ] + ] output_data = [ {'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], @@ -159,7 +159,7 @@ def test_send_to_store(self): {'Orange': [1, 'Aisle 4', False], 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, - ] + ] for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): From 125a3f69038cce43c6f74176710088da1858b4fe Mon Sep 17 00:00:00 2001 From: colinleach Date: Tue, 5 Mar 2024 01:33:10 -0700 Subject: [PATCH 629/826] [Circular Buffer] draft approaches (#3640) * [Circular Buffer] draft approaches * introduction - add guidance * Links and Additions Added `memoryview`, `buffer protocol`, `array.array` and supporting links for various things. --------- Co-authored-by: BethanyG --- .../.approaches/built-in-types/content.md | 91 ++++++++++++++ .../.approaches/built-in-types/snippet.txt | 5 + .../circular-buffer/.approaches/config.json | 30 +++++ .../.approaches/introduction.md | 100 +++++++++++++++ .../.approaches/standard-library/content.md | 119 ++++++++++++++++++ .../.approaches/standard-library/snippet.txt | 4 + 6 files changed, 349 insertions(+) create mode 100644 exercises/practice/circular-buffer/.approaches/built-in-types/content.md create mode 100644 exercises/practice/circular-buffer/.approaches/built-in-types/snippet.txt create mode 100644 exercises/practice/circular-buffer/.approaches/config.json create mode 100644 exercises/practice/circular-buffer/.approaches/introduction.md create mode 100644 exercises/practice/circular-buffer/.approaches/standard-library/content.md create mode 100644 exercises/practice/circular-buffer/.approaches/standard-library/snippet.txt diff --git a/exercises/practice/circular-buffer/.approaches/built-in-types/content.md b/exercises/practice/circular-buffer/.approaches/built-in-types/content.md new file mode 100644 index 0000000000..616153969f --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/built-in-types/content.md @@ -0,0 +1,91 @@ +# Built In Types + + +```python +class CircularBuffer: + def __init__(self, capacity: int) -> None: + self.capacity = capacity + self.content = [] + + def read(self) -> str: + if not self.content: + raise BufferEmptyException("Circular buffer is empty") + return self.content.pop(0) + + def write(self, data: str) -> None: + if len(self.content) == self.capacity: + raise BufferFullException("Circular buffer is full") + self.content.append(data) + + def overwrite(self, data: str) -> None: + if len(self.content) == self.capacity: + self.content.pop(0) + self.write(data) + + def clear(self) -> None: + self.content = [] +``` + +In Python, the `list` type is ubiquitous and exceptionally versatile. +Code similar to that shown above is a very common way to implement this exercise. +Though lists can do much more, here we use `append()` to add an entry to the end of the list, and `pop(0)` to remove an entry from the beginning. + + +By design, lists have no built-in length limit and can grow arbitrarily, so the main task of the programmer here is to keep track of capacity, and limit it when needed. +A `list` is also designed to hold an arbitrary mix of Python objects, and this flexibility in content is emphasized over performance. +For more precise control, at the price of some increased programming complexity, it is possible to use a [`bytearray`][bytearray], or the [`array.array`][array.array] type from the [array][[array-module] module. +For details on using `array.array`, see the [standard library][approaches-standard-library] approach. + +In the case of a `bytearray`, entries are of fixed type: integers in the range `0 <= n < 256`. + +The tests are designed such that this is sufficient to solve the exercise, and byte handling may be quite a realistic view of how circular buffers are often used in practice. + +The code below shows an implementation using this lower-level collection class: + + +```python +class CircularBuffer: + def __init__(self, capacity): + self.capacity = bytearray(capacity) + self.read_start = 0 + self.write_start = 0 + + def read(self): + if not any(self.capacity): + raise BufferEmptyException('Circular buffer is empty') + + data = chr(self.capacity[self.read_start]) + self.capacity[self.read_start] = 0 + self.read_start = (self.read_start + 1) % len(self.capacity) + + return data + + def write(self, data): + if all(self.capacity): + raise BufferFullException('Circular buffer is full') + + try: + self.capacity[self.write_start] = data + except TypeError: + self.capacity[self.write_start] = ord(data) + + self.write_start = (self.write_start + 1) % len(self.capacity) + + def overwrite(self, data): + try: + self.capacity[self.write_start] = data + except TypeError: + self.capacity[self.write_start] = ord(data) + + if all(self.capacity) and self.write_start == self.read_start: + self.read_start = (self.read_start + 1) % len(self.capacity) + self.write_start = (self.write_start + 1) % len(self.capacity) + + def clear(self): + self.capacity = bytearray(len(self.capacity)) +``` + +[approaches-standard-library]: https://exercism.org/tracks/python/exercises/circular-buffer/approaches/standard-library +[array-module]: https://docs.python.org/3/library/array.html#module-array +[array.array]: https://docs.python.org/3/library/array.html#array.array +[bytearray]: https://docs.python.org/3/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview diff --git a/exercises/practice/circular-buffer/.approaches/built-in-types/snippet.txt b/exercises/practice/circular-buffer/.approaches/built-in-types/snippet.txt new file mode 100644 index 0000000000..bf0bc4cb18 --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/built-in-types/snippet.txt @@ -0,0 +1,5 @@ +from queue import Queue + +class CircularBuffer: + def __init__(self, capacity): + self.buffer = Queue(capacity) diff --git a/exercises/practice/circular-buffer/.approaches/config.json b/exercises/practice/circular-buffer/.approaches/config.json new file mode 100644 index 0000000000..3773282790 --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/config.json @@ -0,0 +1,30 @@ +{ + "introduction": { + "authors": [ + "colinleach", + "BethanyG" + ] + }, + "approaches": [ + { + "uuid": "a560804f-1486-451d-98ab-31251926881e", + "slug": "built-in-types", + "title": "Built In Types", + "blurb": "Use a Python list or bytearray.", + "authors": [ + "colinleach", + "BethanyG" + ] + }, + { + "uuid": "f01b8a10-a3d9-4779-9a8b-497310fcbc73", + "slug": "standard-library", + "title": "Standard Library", + "blurb": "Use a Queue or deque object for an easier implementation.", + "authors": [ + "colinleach", + "BethanyG" + ] + } + ] +} diff --git a/exercises/practice/circular-buffer/.approaches/introduction.md b/exercises/practice/circular-buffer/.approaches/introduction.md new file mode 100644 index 0000000000..920d469cde --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/introduction.md @@ -0,0 +1,100 @@ +# Introduction + +The key to this exercise is to: + +- Create a suitable collection object to hold the values. +- Keep track of size as elements are added and removed. + +## General Guidance + +Approaches to this exercise vary from easy but rather boring, to complex but educational. + +It would be useful to think about what you want from completing the exercise, then choose an appropriate collection class that fits your aims. + + +## Exception classes + +All the approaches rely on being able to raise two custom exceptions, with suitable error messages. + +```python +class BufferFullException(BufferError): + """Exception raised when CircularBuffer is full.""" + + def __init__(self, message): + self.message = message + +class BufferEmptyException(BufferError): + """Exception raised when CircularBuffer is empty.""" + + def __init__(self, message): + self.message = message +``` + +Code for these error handling scenarios is always quite similar, so for brevity this aspect will be omitted from the various approaches to the exercise. + + +## Approach: Using built-in types + +Python has an exceptionally flexible and widely-used `list` type. +Most submitted solutions to `Circular Buffer` are based on this data type. + +A less versatile variants include [`bytearray`][bytearray] and [`array.array`][array.array]. + +`bytearray`s are similar to `list`s in many ways, but they are limited to holding only bytes (_represented as integers in the range `0 <= n < 256`_). + +For details, see the [built-in types][approaches-built-in] approach. + + +Finally, [`memoryview`s][memoryview] allow for direct access to the binary data (_ without copying_) of Python objects that support the [`Buffer Protocol`][buffer-protocol]. + `memoryview`s can be used to directly access the underlying memory of types such as `bytearray`, `array.array`, `queue`, `dequeue`, and `list` as well as working with [ctypes][ctypes] from outside libraries and C [structs][struct]. + + For additional information on the `buffer protocol`, see [Emulating Buffer Types][emulating-buffer-types] in the Python documentation. + As of Python `3.12`, the abstract class [collections.abc.Buffer][abc-Buffer] is also available for classes that provide the [`__buffer__()`][dunder-buffer] method and implement the `buffer protocol`. + + +## Approach: Using collection classes from the standard library + +A circular buffer is a type of fixed-size queue, and Python provides various implementations of this very useful type of collection. + +- The [`queue`][queue-module] module contains the [`Queue`][Queue-class] class, which can be initialized with a maximum capacity. +- The [`collections`][collections-module] module contains a [`deque`][deque-class] class (short for Double Ended QUEue), which can also be set to a maximum capacity. +- The [`array`][array.array] module contains an [`array`][array-array] class that is similar to Python's built-in `list`, but is limited to a single datatype (_available datatypes are mapped to C datatypes_). +This allows values to be stored in a more compact and efficient fashion. + + +For details, see the [standard library][approaches-standard-library] approach. + + +## Which Approach to Use? + +Anyone just wanting to use a circular buffer to get other things done and is not super-focused on performance is likely to pick a `Queue` or `deque`, as either of these will handle much of the low-level bookkeeping. + +For a more authentic learning experience, using a `list` will provide practice in keeping track of capacity, with `bytearray` or `array.array` taking the capacity and read/write tracking a stage further. + + +For a really deep dive into low-level Python operations, you can explore using `memoryview`s into `bytearray`s or [`numpy` arrays][numpy-arrays], or customize your own `buffer protocol`-supporting Python object, `ctype` or `struct`. +Some 'jumping off' articles for this are [circular queue or ring buffer (Python and C)][circular-buffer], [memoryview Python Performance][memoryview-py-performance], and [Less Copies in Python with the buffer protocol and memoryviews][less-copies-in-Python]. + + +In reality, anyone wanting to get a deeper understanding of how these collection structures work "from scratch" might do even better to try solving the exercise in a statically-typed system language such as C, Rust, or even try an assembly language like MIPS. + +[Queue-class]: https://docs.python.org/3.11/library/queue.html#queue.Queue +[abc-Buffer]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Buffer +[approaches-built-in]: https://exercism.org/tracks/python/exercises/circular-buffer/approaches/built-in-types +[approaches-standard-library]: https://exercism.org/tracks/python/exercises/circular-buffer/approaches/standard-library +[array-array]: https://docs.python.org/3.11/library/array.html#array.array +[array.array]: https://docs.python.org/3.11/library/array.html#module-array +[buffer-protocol]: https://docs.python.org/3/c-api/buffer.html +[bytearray]: https://docs.python.org/3/library/stdtypes.html#bytearray +[circular-buffer]: https://towardsdatascience.com/circular-queue-or-ring-buffer-92c7b0193326 +[collections-module]: https://docs.python.org/3.11/library/collections.html +[ctypes]: https://docs.python.org/3/library/ctypes.html +[deque-class]: https://docs.python.org/3.11/library/collections.html#collections.deque +[dunder-buffer]: https://docs.python.org/3/reference/datamodel.html#object.__buffer__ +[emulating-buffer-types]: https://docs.python.org/3/reference/datamodel.html#emulating-buffer-types +[less-copies-in-Python]: https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews +[memoryview-py-performance]: https://prrasad.medium.com/memory-view-python-performance-improvement-method-c241a79e9843 +[memoryview]: https://docs.python.org/3/library/stdtypes.html#memoryview +[numpy-arrays]: https://numpy.org/doc/stable/reference/generated/numpy.array.html +[queue-module]: https://docs.python.org/3.11/library/queue.html +[struct]: https://docs.python.org/3/library/struct.html diff --git a/exercises/practice/circular-buffer/.approaches/standard-library/content.md b/exercises/practice/circular-buffer/.approaches/standard-library/content.md new file mode 100644 index 0000000000..2a1acf5d67 --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/standard-library/content.md @@ -0,0 +1,119 @@ +# Standard Library + + +```python +from queue import Queue + +class CircularBuffer: + def __init__(self, capacity): + self.buffer = Queue(capacity) + + def read(self): + if self.buffer.empty(): + raise BufferEmptyException("Circular buffer is empty") + return self.buffer.get() + + def write(self, data): + if self.buffer.full(): + raise BufferFullException("Circular buffer is full") + self.buffer.put(data) + + def overwrite(self, data): + if self.buffer.full(): + _ = self.buffer.get() + self.buffer.put(data) + + def clear(self): + while not self.buffer.empty(): + _ = self.buffer.get() +``` + +The above code uses a [`Queue` object][queue] to "implement" the buffer, a collection class which assumes entries will be added at the end and removed at the beginning. +This is a "queue" in British English, though Americans call it a "line". + + +Alternatively, the `collections` module provides a [`deque` object][deque], a double-ended queue class. +A `deque` allows adding and removing entries at both ends, which is not something we need for a circular buffer. +However, the syntax may be even more concise than for a `queue`: + + +```python +from collections import deque +from typing import Any + +class CircularBuffer: + def __init__(self, capacity: int): + self.buffer = deque(maxlen=capacity) + + def read(self) -> Any: + if len(self.buffer) == 0: + raise BufferEmptyException("Circular buffer is empty") + return self.buffer.popleft() + + def write(self, data: Any) -> None: + if len(self.buffer) == self.buffer.maxlen: + raise BufferFullException("Circular buffer is full") + self.buffer.append(data) + + def overwrite(self, data: Any) -> None: + self.buffer.append(data) + + def clear(self) -> None: + if len(self.buffer) > 0: + self.buffer.popleft() +``` + +Both `Queue` and `deque` have the ability to limit the queues length by declaring a 'capacity' or 'maxlen' attribute. +This simplifies empty/full and read/write tracking. + + +Finally, the [`array`][array-array] class from the [`array`][array.array] module can be used to initialize a 'buffer' that works similarly to a built-in `list` or `bytearray`, but with efficiencies in storage and access: + + +```python +from array import array + + +class CircularBuffer: + def __init__(self, capacity): + self.buffer = array('u') + self.capacity = capacity + self.marker = 0 + + def read(self): + if not self.buffer: + raise BufferEmptyException('Circular buffer is empty') + + else: + data = self.buffer.pop(self.marker) + if self.marker > len(self.buffer)-1: self.marker = 0 + + return data + + def write(self, data): + if len(self.buffer) < self.capacity: + try: + self.buffer.append(data) + except TypeError: + self.buffer.append(data) + + else: raise BufferFullException('Circular buffer is full') + + def overwrite(self, data): + if len(self.buffer) < self.capacity: self.buffer.append(data) + + else: + self.buffer[self.marker] = data + + if self.marker < self.capacity - 1: self.marker += 1 + else: self.marker = 0 + + def clear(self): + self.marker = 0 + self.buffer = array('u') +``` + +[queue]: https://docs.python.org/3/library/queue.html +[deque]: https://docs.python.org/3/library/collections.html#deque-objects +[array-array]: https://docs.python.org/3.11/library/array.html#array.array +[array.array]: https://docs.python.org/3.11/library/array.html#module-array diff --git a/exercises/practice/circular-buffer/.approaches/standard-library/snippet.txt b/exercises/practice/circular-buffer/.approaches/standard-library/snippet.txt new file mode 100644 index 0000000000..51114cadba --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/standard-library/snippet.txt @@ -0,0 +1,4 @@ +class CircularBuffer: + def __init__(self, capacity: int) -> None: + self.capacity = capacity + self.content = [] From daae57bca6ccb224ba213cd6e54060b3be31e7ef Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 5 Mar 2024 06:55:34 -0800 Subject: [PATCH 630/826] Synced Two Bucket to Problem Specs. Instructions updated. (#3655) --- exercises/practice/two-bucket/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/two-bucket/.docs/instructions.md b/exercises/practice/two-bucket/.docs/instructions.md index 7249deb361..30d779aa92 100644 --- a/exercises/practice/two-bucket/.docs/instructions.md +++ b/exercises/practice/two-bucket/.docs/instructions.md @@ -11,7 +11,7 @@ There are some rules that your solution must follow: b) the second bucket is full 2. Emptying a bucket and doing nothing to the other. 3. Filling a bucket and doing nothing to the other. -- After an action, you may not arrive at a state where the starting bucket is empty and the other bucket is full. +- After an action, you may not arrive at a state where the initial starting bucket is empty and the other bucket is full. Your program will take as input: From efc33f46d2197a718ddf80f5501788062697bc57 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 5 Mar 2024 07:14:34 -0800 Subject: [PATCH 631/826] Updated tests.toml and regenerated test cases for the Change exercise. (#3656) New test case added. Retesting needed. See Prob-Specs PR#2385: https://github.com/exercism/problem-specifications/pull/2385 --- exercises/practice/change/.meta/tests.toml | 3 +++ exercises/practice/change/change_test.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/exercises/practice/change/.meta/tests.toml b/exercises/practice/change/.meta/tests.toml index d2cf3ed902..2d2f44bc21 100644 --- a/exercises/practice/change/.meta/tests.toml +++ b/exercises/practice/change/.meta/tests.toml @@ -33,6 +33,9 @@ description = "possible change without unit coins available" [9a166411-d35d-4f7f-a007-6724ac266178] description = "another possible change without unit coins available" +[ce0f80d5-51c3-469d-818c-3e69dbd25f75] +description = "a greedy approach is not optimal" + [bbbcc154-e9e9-4209-a4db-dd6d81ec26bb] description = "no coins make 0 change" diff --git a/exercises/practice/change/change_test.py b/exercises/practice/change/change_test.py index f8699e2f6d..584edee454 100644 --- a/exercises/practice/change/change_test.py +++ b/exercises/practice/change/change_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json -# File last updated on 2023-07-20 +# File last updated on 2024-03-05 import unittest @@ -37,6 +37,9 @@ def test_possible_change_without_unit_coins_available(self): def test_another_possible_change_without_unit_coins_available(self): self.assertEqual(find_fewest_coins([4, 5], 27), [4, 4, 4, 5, 5, 5]) + def test_a_greedy_approach_is_not_optimal(self): + self.assertEqual(find_fewest_coins([1, 10, 11], 20), [10, 10]) + def test_no_coins_make_0_change(self): self.assertEqual(find_fewest_coins([1, 5, 10, 21, 25], 0), []) From 59c98a03597e82c00f52c8e065f41a74dcf9d982 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 5 Mar 2024 08:35:39 -0800 Subject: [PATCH 632/826] [Circular Buffer Approaches]: Update introduction.md ln 41 (#3657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrected the tense mismatch on line 41. 😄 --- exercises/practice/circular-buffer/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/circular-buffer/.approaches/introduction.md b/exercises/practice/circular-buffer/.approaches/introduction.md index 920d469cde..55fbc81bc2 100644 --- a/exercises/practice/circular-buffer/.approaches/introduction.md +++ b/exercises/practice/circular-buffer/.approaches/introduction.md @@ -38,7 +38,7 @@ Code for these error handling scenarios is always quite similar, so for brevity Python has an exceptionally flexible and widely-used `list` type. Most submitted solutions to `Circular Buffer` are based on this data type. -A less versatile variants include [`bytearray`][bytearray] and [`array.array`][array.array]. +Less versatile variants include [`bytearray`][bytearray] and [`array.array`][array.array]. `bytearray`s are similar to `list`s in many ways, but they are limited to holding only bytes (_represented as integers in the range `0 <= n < 256`_). From d06f9f15d7c63cf10a407597dac3ca01357a61f3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 6 Mar 2024 11:25:06 -0800 Subject: [PATCH 633/826] First draft of representations.md doc (#3570) --- exercises/shared/.docs/representations.md | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 exercises/shared/.docs/representations.md diff --git a/exercises/shared/.docs/representations.md b/exercises/shared/.docs/representations.md new file mode 100644 index 0000000000..8439ca9aab --- /dev/null +++ b/exercises/shared/.docs/representations.md @@ -0,0 +1,34 @@ +# Representations + +The [Python representer][representer] processes and normalizes student solutions into a more "generic" form: + +- Code is converted to an AST using the [Python AST module][python-ast]. +- Final AST tree is converted to a string without whitespace or indentation, and output as `representation.txt`. +- For troubleshooting purposes, `representation.out` includes starting AST, edited AST, and a code representation with normalizations applied. + +- Removals: + - typehints + - `print()` statements + - `if __name__ == __main__` blocks + - comments + - docstrings + +- Replacements: + - user-defined names are replaced with placeholders (_including function names, parameters, and variables in lambdas_) + +- Normalizations: + - stringquotes `'` are changed to `"` (_doublequotes_), unless escapes are needed, then they remain unchanged. + - number literals have any underscores removed and scientific notation is calculated by place: + - **66_777_888_433** --> 66777888433 + - **1_999_878_473.66** --> 1999878473.66 + - **77_555_998_125.445_779** --> 77555998125.44579 + - **44_573_123.445_312+123_674.889_12j** --> 44573123.445312 + 123674.88912j + - **1e6** --> 1000000.0 #1000000 + - **1e2+.23** --> 100.0 + 0.23 #100.23 + - **1e2+1_23e0+4.4e-1** --> 100.0 + 123.0 + 0.44 #223.44 + - **7e6+7e5+5e4+9.98e2+4.45_779e-1** -->7000000.0 + 700000.0 + 50000.0 + 998.0 + 0.445779 #7750998.445779 + - **(7e6+7e5+5e4+9.98e1+4.457_79e-1)+(1e2+1.23e1+4.444_23e-1)*1*j** --> (7000000.0 + 700000.0 + 50000.0 + 99.8 + 0.445779 + (100.0 + 12.3 + 0.444423) * 1j) #7750100.245779+112.744423j + +[representer]: https://github.com/exercism/python-representer/tree/main/representer +[python-ast]: https://docs.python.org/3/library/ast.html#module-ast + From 07a3adf9891ce19c0def15d912ac11542e311175 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:36:40 -0700 Subject: [PATCH 634/826] Bump actions/checkout from 4.1.1 to 4.1.2 (#3658) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/b4ffde65f46336ab88eb53be808477a3936bae11...9bb56186c3b09b4f86b1c65136769dd318469633) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 944c2f531f..1ea5b87bcb 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Set up Python uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 37a459ce50..9eb4d90d97 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 1228b8f104..35c9109251 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Run test-runner run: docker-compose run test-runner From 6c9fb4de60316950b78ff7413d30e492a1a2e998 Mon Sep 17 00:00:00 2001 From: fefulowe <15332193+fefulowe@users.noreply.github.com> Date: Sat, 23 Mar 2024 17:13:13 -0300 Subject: [PATCH 635/826] =?UTF-8?q?[Black=20Jack]:=20=E2=9C=8F=EF=B8=8F=20?= =?UTF-8?q?Updates=20to=20instructions.=20(#3660)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✏️ Update instructions.md if we already have an ace, the upcoming ace's value should be 1 https://en.wikipedia.org/wiki/Blackjack?lang=en#Rules_of_play_at_casinos [...] and aces count as either 1 or 11 according to the player's choice. * ✏️ Update instructions.md The instructions on the exercise [black_jack](https://exercism.org/tracks/python/exercises/black-jack) might be improved. It could be better to refer to the value of the upcoming ace instead of the value of those in hand since this doesn't apply if we have two aces in hand. Current instructions: `Hint: if we already have an ace in hand then its value would be 11.` Proposed instructions: `Hint: if we already have an ace in hand, then the value for the upcoming ace would be 1.` Sources: "[...] and aces count as either 1 or 11 according to the player's choice." from [Wikipedia](https://en.wikipedia.org/wiki/Blackjack?lang=en#Rules_of_play_at_casinos). --------- Co-authored-by: Federico Ponce de Leon --- exercises/concept/black-jack/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/black-jack/.docs/instructions.md b/exercises/concept/black-jack/.docs/instructions.md index d6c8fc502f..21fc971e23 100644 --- a/exercises/concept/black-jack/.docs/instructions.md +++ b/exercises/concept/black-jack/.docs/instructions.md @@ -67,7 +67,7 @@ Define the `value_of_ace(, )` function with parameters `card Your function will have to decide if the upcoming ace will get a value of 1 or a value of 11, and return that value. Remember: the value of the hand with the ace needs to be as high as possible _without_ going over 21. -**Hint**: if we already have an ace in hand then its value would be 11. +**Hint**: if we already have an ace in hand, then the value for the upcoming ace would be 1. ```python >>> value_of_ace('6', 'K') From 5c3ff61a051e5ce871fd7b73304b2c5de09f9239 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Wed, 27 Mar 2024 17:14:34 +0000 Subject: [PATCH 636/826] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3662)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/45ce43faa93a84c84f407748aae3aa028383ec77 --- .github/workflows/no-important-files-changed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index b940c5991c..812e912966 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -16,7 +16,7 @@ permissions: pull-requests: write jobs: - pause: + check: uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main with: repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} From d8b3979fa69fce8b726fbb35d3dcc43ebbd3bb0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:15:09 -0700 Subject: [PATCH 637/826] Bump actions/setup-python from 5.0.0 to 5.1.0 (#3661) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/0a5c61591373683505ea898e09a3ea4f39ef2b9c...82c7e631bb3cdc910f68e0081d67478d79c6982d) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 1ea5b87bcb..7168ccd5d3 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Set up Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: python-version: ${{ matrix.python-version }} From 41366ab8713aca657ad5b76ad52ed539aa04a161 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 31 Mar 2024 20:19:26 +0200 Subject: [PATCH 638/826] Sync the `sieve` exercise's docs with the latest data. (#3664) --- .../practice/sieve/.docs/instructions.md | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 3adf1d551b..085c0a57d9 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,28 +1,42 @@ # Instructions -Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find all prime numbers less than or equal to a given number. -A prime number is a number that is only divisible by 1 and itself. +A prime number is a number larger than 1 that is only divisible by 1 and itself. For example, 2, 3, 5, 7, 11, and 13 are prime numbers. - -The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime. - -A number that is **not** prime is called a "composite number". +By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. Then you repeat the following steps: -1. Find the next unmarked number in your list. This is a prime number. -2. Mark all the multiples of that prime number as composite (not prime). +1. Find the next unmarked number in your list (skipping over marked numbers). + This is a prime number. +2. Mark all the multiples of that prime number as **not** prime. You keep repeating these steps until you've gone through every number in your list. At the end, all the unmarked numbers are prime. ~~~~exercism/note -[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. - The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. -A good first test is to check that you do not use division or remainder operations. - -[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +To check you are implementing the Sieve correctly, a good first test is to check that you do not use division or remainder operations. ~~~~ + +## Example + +Let's say you're finding the primes less than or equal to 10. + +- List out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. +- 2 is unmarked and is therefore a prime. + Mark 4, 6, 8 and 10 as "not prime". +- 3 is unmarked and is therefore a prime. + Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. +- 4 is marked as "not prime", so we skip over it. +- 5 is unmarked and is therefore a prime. + Mark 10 as not prime _(optional - as it's already been marked)_. +- 6 is marked as "not prime", so we skip over it. +- 7 is unmarked and is therefore a prime. +- 8 is marked as "not prime", so we skip over it. +- 9 is marked as "not prime", so we skip over it. +- 10 is marked as "not prime", so we stop as there are no more numbers to check. + +You've examined all numbers and found 2, 3, 5, and 7 are still unmarked, which means they're the primes less than or equal to 10. From 128196aeb7fbc5ca08bd4c81d01482a01e862c05 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 31 Mar 2024 22:07:53 +0200 Subject: [PATCH 639/826] Sync the `two-fer` exercise's docs with the latest data. (#3665) --- exercises/practice/two-fer/.docs/instructions.md | 5 ++--- exercises/practice/two-fer/.docs/introduction.md | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md index 37aa75297e..adc5348798 100644 --- a/exercises/practice/two-fer/.docs/instructions.md +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -2,14 +2,13 @@ Your task is to determine what you will say as you give away the extra cookie. -If your friend likes cookies, and is named Do-yun, then you will say: +If you know the person's name (e.g. if they're named Do-yun), then you will say: ```text One for Do-yun, one for me. ``` -If your friend doesn't like cookies, you give the cookie to the next person in line at the bakery. -Since you don't know their name, you will say _you_ instead. +If you don't know the person's name, you will say _you_ instead. ```text One for you, one for me. diff --git a/exercises/practice/two-fer/.docs/introduction.md b/exercises/practice/two-fer/.docs/introduction.md index 8c124394aa..5947a2230b 100644 --- a/exercises/practice/two-fer/.docs/introduction.md +++ b/exercises/practice/two-fer/.docs/introduction.md @@ -5,4 +5,4 @@ Two-for-one is a way of saying that if you buy one, you also get one for free. So the phrase "two-fer" often implies a two-for-one offer. Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). -You go for the offer and (very generously) decide to give the extra cookie to a friend. +You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. From ae694338c1b69f8fa075dea14ec635b68c3e1305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 1 Apr 2024 20:08:32 +0200 Subject: [PATCH 640/826] Fix maximum length of a roman numeral (#3667) --- exercises/practice/roman-numerals/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md index 4468862f0c..35f73e3ace 100644 --- a/exercises/practice/roman-numerals/.approaches/introduction.md +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -5,7 +5,7 @@ There is no single, obvious solution to this exercise, but a diverse array of wo ## General guidance Roman numerals are limited to positive integers from 1 to 3999 (MMMCMXCIX). -In the version used for this exercise, the longest string needed to represent a Roman numeral is 14 characters (MMDCCCLXXXVIII). +In the version used for this exercise, the longest string needed to represent a Roman numeral is 15 characters (MMMDCCCLXXXVIII). Minor variants of the system have been used which represent 4 as IIII rather than IV, allowing for longer strings, but those are not relevant here. The system is inherently decimal: the number of human fingers has not changed since ancient Rome, nor the habit of using them for counting. From c421d43f55cb7715bf0eadc240beed97ece7f32a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 1 Apr 2024 19:08:15 -0700 Subject: [PATCH 641/826] Update resistor_color_expert_test.py (#3669) [no important files changed] Per [issue on forum](https://forum.exercism.org/t/python-resistor-color-expert/10587). --- .../resistor-color-expert/resistor_color_expert_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py index bcf2052c02..2ba4b877d9 100644 --- a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py +++ b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py @@ -35,7 +35,7 @@ def test_red_green_yellow_yellow_and_brown(self): resistor_label(["red", "green", "yellow", "yellow", "brown"]), "2.54 megaohms ±1%" ) - def test_blue_grey_white_red_and_brown(self): + def test_blue_grey_white_brown_and_brown(self): self.assertEqual( resistor_label(["blue", "grey", "white", "brown", "brown"]), "6.89 kiloohms ±1%" ) From da6e28aad23941f951dd5be062b9fcada91b266d Mon Sep 17 00:00:00 2001 From: Dmitry Cheremushkin Date: Sun, 14 Apr 2024 19:50:05 +0300 Subject: [PATCH 642/826] [Essays] Fix a typo in the exercise introduction (#3672) --- concepts/string-methods/about.md | 2 +- exercises/concept/little-sisters-essay/.docs/introduction.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/string-methods/about.md b/concepts/string-methods/about.md index 983e604e71..308b89d9d6 100644 --- a/concepts/string-methods/about.md +++ b/concepts/string-methods/about.md @@ -43,7 +43,7 @@ There may also be [locale][locale] rules in place for a language or character se ```python >>> man_in_hat_th = 'ผู้ชายใส่หมวก' ->>> man_in_hat_ru = 'mужчина в шляпе' +>>> man_in_hat_ru = 'мужчина в шляпе' >>> man_in_hat_ko = '모자를 쓴 남자' >>> man_in_hat_en = 'the man in the hat.' diff --git a/exercises/concept/little-sisters-essay/.docs/introduction.md b/exercises/concept/little-sisters-essay/.docs/introduction.md index 204c893e92..fc327a1250 100644 --- a/exercises/concept/little-sisters-essay/.docs/introduction.md +++ b/exercises/concept/little-sisters-essay/.docs/introduction.md @@ -22,7 +22,7 @@ There may also be [locale][locale] rules in place for a language or character se ```python man_in_hat_th = 'ผู้ชายใส่หมวก' -man_in_hat_ru = 'mужчина в шляпе' +man_in_hat_ru = 'мужчина в шляпе' man_in_hat_ko = '모자를 쓴 남자' man_in_hat_en = 'the man in the hat.' From cca033bc93c6312051f82dd7bf61b3860d8ec56e Mon Sep 17 00:00:00 2001 From: Dmitry Cheremushkin Date: Tue, 16 Apr 2024 02:23:17 +0300 Subject: [PATCH 643/826] [Card Games] Update 'approx_average_is_average' docstring summary (#3675) Co-authored-by: Dmitrii Cheremushkin --- exercises/concept/card-games/.meta/exemplar.py | 2 +- exercises/concept/card-games/lists.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/card-games/.meta/exemplar.py b/exercises/concept/card-games/.meta/exemplar.py index 840aea6aa9..d6531f0186 100644 --- a/exercises/concept/card-games/.meta/exemplar.py +++ b/exercises/concept/card-games/.meta/exemplar.py @@ -47,7 +47,7 @@ def card_average(hand): def approx_average_is_average(hand): - """Return if an average is using (first + last index values ) OR ('middle' card) == calculated average. + """Return if the (average of first and last card values) OR ('middle' card) == calculated average. :param hand: list - cards in hand. :return: bool - does one of the approximate averages equal the `true average`? diff --git a/exercises/concept/card-games/lists.py b/exercises/concept/card-games/lists.py index 11dff666de..03fb417330 100644 --- a/exercises/concept/card-games/lists.py +++ b/exercises/concept/card-games/lists.py @@ -47,7 +47,7 @@ def card_average(hand): def approx_average_is_average(hand): - """Return if an average is using (first + last index values ) OR ('middle' card) == calculated average. + """Return if the (average of first and last card values) OR ('middle' card) == calculated average. :param hand: list - cards in hand. :return: bool - does one of the approximate averages equal the `true average`? From 8e849c025ce831769abaff4d3cf178af6909421b Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:45:47 +0200 Subject: [PATCH 644/826] Sync the `zebra-puzzle` exercise's docs with the latest data. (#3676) --- .../practice/zebra-puzzle/.docs/instructions.md | 16 ++++++++++++---- .../practice/zebra-puzzle/.docs/introduction.md | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 exercises/practice/zebra-puzzle/.docs/introduction.md diff --git a/exercises/practice/zebra-puzzle/.docs/instructions.md b/exercises/practice/zebra-puzzle/.docs/instructions.md index 6d62d18e4c..c666e33cb3 100644 --- a/exercises/practice/zebra-puzzle/.docs/instructions.md +++ b/exercises/practice/zebra-puzzle/.docs/instructions.md @@ -1,6 +1,13 @@ # Instructions -Solve the zebra puzzle. +Your task is to solve the Zebra Puzzle to find the answer to these two questions: + +- Which of the residents drinks water? +- Who owns the zebra? + +## Puzzle + +The following 15 statements are all known to be true: 1. There are five houses. 2. The Englishman lives in the red house. @@ -18,7 +25,8 @@ Solve the zebra puzzle. 14. The Japanese smokes Parliaments. 15. The Norwegian lives next to the blue house. -Each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and smoke different brands of cigarettes. +Additionally, each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and smoke different brands of cigarettes. -Which of the residents drinks water? -Who owns the zebra? +~~~~exercism/note +There are 24 billion (5!⁵ = 24,883,200,000) possible solutions, so try ruling out as many solutions as possible. +~~~~ diff --git a/exercises/practice/zebra-puzzle/.docs/introduction.md b/exercises/practice/zebra-puzzle/.docs/introduction.md new file mode 100644 index 0000000000..c8b7ef751e --- /dev/null +++ b/exercises/practice/zebra-puzzle/.docs/introduction.md @@ -0,0 +1,14 @@ +# Introduction + +The Zebra Puzzle is a famous logic puzzle in which there are five houses, each painted a different color. +The houses have different inhabitants, who have different nationalities, own different pets, drink different beverages and smoke different brands of cigarettes. + +To help you solve the puzzle, you're given 15 statements describing the solution. +However, only by combining the information in _all_ statements will you be able to find the solution to the puzzle. + +~~~~exercism/note +The Zebra Puzzle is a [Constraint satisfaction problem (CSP)][constraint-satisfaction-problem]. +In such a problem, you have a set of possible values and a set of constraints that limit which values are valid. +Another well-known CSP is Sudoku. +[constraint-satisfaction-problem]: https://en.wikipedia.org/wiki/Constraint_satisfaction_problem +~~~~ From aaa8c4d553c99f1491780f99652cda309dddb7a0 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:46:22 +0200 Subject: [PATCH 645/826] Sync the `scrabble-score` exercise's docs with the latest data. (#3677) --- .../scrabble-score/.docs/instructions.md | 47 +++++++------------ .../scrabble-score/.docs/introduction.md | 7 +++ 2 files changed, 23 insertions(+), 31 deletions(-) create mode 100644 exercises/practice/scrabble-score/.docs/introduction.md diff --git a/exercises/practice/scrabble-score/.docs/instructions.md b/exercises/practice/scrabble-score/.docs/instructions.md index 3f986c947b..738f928c5b 100644 --- a/exercises/practice/scrabble-score/.docs/instructions.md +++ b/exercises/practice/scrabble-score/.docs/instructions.md @@ -1,40 +1,25 @@ # Instructions -Given a word, compute the Scrabble score for that word. +Your task is to compute a word's Scrabble score by summing the values of its letters. -## Letter Values +The letters are valued as follows: -You'll need these: +| Letter | Value | +| ---------------------------- | ----- | +| A, E, I, O, U, L, N, R, S, T | 1 | +| D, G | 2 | +| B, C, M, P | 3 | +| F, H, V, W, Y | 4 | +| K | 5 | +| J, X | 8 | +| Q, Z | 10 | -```text -Letter Value -A, E, I, O, U, L, N, R, S, T 1 -D, G 2 -B, C, M, P 3 -F, H, V, W, Y 4 -K 5 -J, X 8 -Q, Z 10 -``` - -## Examples - -"cabbage" should be scored as worth 14 points: +For example, the word "cabbage" is worth 14 points: - 3 points for C -- 1 point for A, twice -- 3 points for B, twice +- 1 point for A +- 3 points for B +- 3 points for B +- 1 point for A - 2 points for G - 1 point for E - -And to total: - -- `3 + 2*1 + 2*3 + 2 + 1` -- = `3 + 2 + 6 + 3` -- = `5 + 9` -- = 14 - -## Extensions - -- You can play a double or a triple letter. -- You can play a double or a triple word. diff --git a/exercises/practice/scrabble-score/.docs/introduction.md b/exercises/practice/scrabble-score/.docs/introduction.md new file mode 100644 index 0000000000..8821f240ba --- /dev/null +++ b/exercises/practice/scrabble-score/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Scrabble][wikipedia] is a word game where players place letter tiles on a board to form words. +Each letter has a value. +A word's score is the sum of its letters' values. + +[wikipedia]: https://en.wikipedia.org/wiki/Scrabble From fd646d7bcf702895e5af9fb6685c1de0d32619e7 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:46:51 +0200 Subject: [PATCH 646/826] Sync the `minesweeper` exercise's docs with the latest data. (#3678) --- .../minesweeper/.docs/instructions.md | 20 +++++++------------ .../minesweeper/.docs/introduction.md | 5 +++++ 2 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/minesweeper/.docs/introduction.md diff --git a/exercises/practice/minesweeper/.docs/instructions.md b/exercises/practice/minesweeper/.docs/instructions.md index f5f918bdff..7c1df2e4ba 100644 --- a/exercises/practice/minesweeper/.docs/instructions.md +++ b/exercises/practice/minesweeper/.docs/instructions.md @@ -1,19 +1,13 @@ # Instructions -Add the mine counts to a completed Minesweeper board. +Your task is to add the mine counts to empty squares in a completed Minesweeper board. +The board itself is a rectangle composed of squares that are either empty (`' '`) or a mine (`'*'`). -Minesweeper is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. +For each empty square, count the number of mines adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent mines, leave it empty. +Otherwise replace it with the adjacent mines count. -In this exercise you have to create some code that counts the number of mines adjacent to a given empty square and replaces that square with the count. - -The board is a rectangle composed of blank space (' ') characters. -A mine is represented by an asterisk (`*`) character. - -If a given space has no adjacent mines at all, leave that square blank. - -## Examples - -For example you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): ```text ·*·*· @@ -22,7 +16,7 @@ For example you may receive a 5 x 4 board like this (empty spaces are represente ····· ``` -And your code will transform it into this: +Which your code should transform into this: ```text 1*3*1 diff --git a/exercises/practice/minesweeper/.docs/introduction.md b/exercises/practice/minesweeper/.docs/introduction.md new file mode 100644 index 0000000000..5f74a742b0 --- /dev/null +++ b/exercises/practice/minesweeper/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +[Minesweeper][wikipedia] is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. + +[wikipedia]: https://en.wikipedia.org/wiki/Minesweeper_(video_game) From 1b8e59abd71e4c51d4eb85aac630241ed968d84b Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:47:21 +0200 Subject: [PATCH 647/826] Sync the `dnd-character` exercise's docs with the latest data. (#3679) --- .../practice/dnd-character/.docs/instructions.md | 11 ++++++----- .../practice/dnd-character/.docs/introduction.md | 10 ++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/dnd-character/.docs/introduction.md diff --git a/exercises/practice/dnd-character/.docs/instructions.md b/exercises/practice/dnd-character/.docs/instructions.md index b0a603591e..e14e7949d6 100644 --- a/exercises/practice/dnd-character/.docs/instructions.md +++ b/exercises/practice/dnd-character/.docs/instructions.md @@ -3,13 +3,13 @@ For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with. This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma. These six abilities have scores that are determined randomly. -You do this by rolling four 6-sided dice and record the sum of the largest three dice. +You do this by rolling four 6-sided dice and recording the sum of the largest three dice. You do this six times, once for each ability. Your character's initial hitpoints are 10 + your character's constitution modifier. You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down. -Write a random character generator that follows the rules above. +Write a random character generator that follows the above rules. For example, the six throws of four dice may look like: @@ -22,10 +22,11 @@ For example, the six throws of four dice may look like: Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6. -## Notes - +~~~~exercism/note Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. One such language is [Troll][troll]. -[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons [troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html +~~~~ + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons diff --git a/exercises/practice/dnd-character/.docs/introduction.md b/exercises/practice/dnd-character/.docs/introduction.md new file mode 100644 index 0000000000..5301f61829 --- /dev/null +++ b/exercises/practice/dnd-character/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +After weeks of anticipation, you and your friends get together for your very first game of [Dungeons & Dragons][dnd] (D&D). +Since this is the first session of the game, each player has to generate a character to play with. +The character's abilities are determined by rolling 6-sided dice, but where _are_ the dice? +With a shock, you realize that your friends are waiting for _you_ to produce the dice; after all it was your idea to play D&D! +Panicking, you realize you forgot to bring the dice, which would mean no D&D game. +As you have some basic coding skills, you quickly come up with a solution: you'll write a program to simulate dice rolls. + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons From 0ad89c596fb1c07e762d3ebb0a40421e6e9af724 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:47:55 +0200 Subject: [PATCH 648/826] Sync the `all-your-base` exercise's docs with the latest data. (#3680) --- .../all-your-base/.docs/instructions.md | 21 +++++++------------ .../all-your-base/.docs/introduction.md | 8 +++++++ 2 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/all-your-base/.docs/introduction.md diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md index 4602b5cfad..1b688b6915 100644 --- a/exercises/practice/all-your-base/.docs/instructions.md +++ b/exercises/practice/all-your-base/.docs/instructions.md @@ -1,14 +1,11 @@ # Instructions -Convert a number, represented as a sequence of digits in one base, to any other base. +Convert a sequence of digits in one base, representing a number, into a sequence of digits in another base, representing the same number. -Implement general base conversion. -Given a number in base **a**, represented as a sequence of digits, convert it to base **b**. - -## Note - -- Try to implement the conversion yourself. - Do not use something else to perform the conversion for you. +~~~~exercism/note +Try to implement the conversion yourself. +Do not use something else to perform the conversion for you. +~~~~ ## About [Positional Notation][positional-notation] @@ -16,17 +13,15 @@ In positional notation, a number in base **b** can be understood as a linear com The number 42, _in base 10_, means: -`(4 * 10^1) + (2 * 10^0)` +`(4 × 10¹) + (2 × 10⁰)` The number 101010, _in base 2_, means: -`(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)` +`(1 × 2⁵) + (0 × 2⁴) + (1 × 2³) + (0 × 2²) + (1 × 2¹) + (0 × 2⁰)` The number 1120, _in base 3_, means: -`(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)` - -I think you got the idea! +`(1 × 3³) + (1 × 3²) + (2 × 3¹) + (0 × 3⁰)` _Yes. Those three numbers above are exactly the same. Congratulations!_ diff --git a/exercises/practice/all-your-base/.docs/introduction.md b/exercises/practice/all-your-base/.docs/introduction.md new file mode 100644 index 0000000000..68aaffbed9 --- /dev/null +++ b/exercises/practice/all-your-base/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You've just been hired as professor of mathematics. +Your first week went well, but something is off in your second week. +The problem is that every answer given by your students is wrong! +Luckily, your math skills have allowed you to identify the problem: the student answers _are_ correct, but they're all in base 2 (binary)! +Amazingly, it turns out that each week, the students use a different base. +To help you quickly verify the student answers, you'll be building a tool to translate between bases. From 567ad1b8db596e1bb423b87011b544aa217b4ec1 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 18 Apr 2024 23:27:03 +0200 Subject: [PATCH 649/826] Sync the `zebra-puzzle` exercise's docs with the latest data. (#3681) --- exercises/practice/zebra-puzzle/.docs/introduction.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/zebra-puzzle/.docs/introduction.md b/exercises/practice/zebra-puzzle/.docs/introduction.md index c8b7ef751e..33d688fd51 100644 --- a/exercises/practice/zebra-puzzle/.docs/introduction.md +++ b/exercises/practice/zebra-puzzle/.docs/introduction.md @@ -10,5 +10,6 @@ However, only by combining the information in _all_ statements will you be able The Zebra Puzzle is a [Constraint satisfaction problem (CSP)][constraint-satisfaction-problem]. In such a problem, you have a set of possible values and a set of constraints that limit which values are valid. Another well-known CSP is Sudoku. + [constraint-satisfaction-problem]: https://en.wikipedia.org/wiki/Constraint_satisfaction_problem ~~~~ From 087d4748c2ec74c1ea9035bd670e23859ee1a8ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:24:08 -0700 Subject: [PATCH 650/826] Bump actions/checkout from 4.1.2 to 4.1.3 (#3682) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.2 to 4.1.3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/9bb56186c3b09b4f86b1c65136769dd318469633...1d96c772d19495a3b5c517cd2bc0cb401ea0529f) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 7168ccd5d3..4aecd9d346 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 9eb4d90d97..28be0f2a35 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 35c9109251..e0ea3ca322 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f - name: Run test-runner run: docker-compose run test-runner From 1fd9f1ccd057aa210c68ca76eaa5e015bf6046fa Mon Sep 17 00:00:00 2001 From: Dmitry Cheremushkin Date: Sun, 21 Apr 2024 20:55:57 +0300 Subject: [PATCH 651/826] [Inventory Management] Update 'list_inventory' docstring summary (#3684) Co-authored-by: Dmitrii Cheremushkin --- exercises/concept/inventory-management/.meta/exemplar.py | 2 +- exercises/concept/inventory-management/dicts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/inventory-management/.meta/exemplar.py b/exercises/concept/inventory-management/.meta/exemplar.py index ac36ce9758..ac02bad30d 100644 --- a/exercises/concept/inventory-management/.meta/exemplar.py +++ b/exercises/concept/inventory-management/.meta/exemplar.py @@ -55,7 +55,7 @@ def remove_item(inventory, item): def list_inventory(inventory): - """Create a list containing all (item_name, item_count) pairs in inventory. + """Create a list containing only available (item_name, item_count > 0) pairs in inventory. :param inventory: dict - an inventory dictionary. :return: list of tuples - list of key, value pairs from the inventory dictionary. diff --git a/exercises/concept/inventory-management/dicts.py b/exercises/concept/inventory-management/dicts.py index ba524bba8c..2600eceb27 100644 --- a/exercises/concept/inventory-management/dicts.py +++ b/exercises/concept/inventory-management/dicts.py @@ -45,7 +45,7 @@ def remove_item(inventory, item): def list_inventory(inventory): - """Create a list containing all (item_name, item_count) pairs in inventory. + """Create a list containing only available (item_name, item_count > 0) pairs in inventory. :param inventory: dict - an inventory dictionary. :return: list of tuples - list of key, value pairs from the inventory dictionary. From e6b9efe1518f3d7d835411c5fcfbcc39fc8f4118 Mon Sep 17 00:00:00 2001 From: Dmitry Cheremushkin Date: Wed, 24 Apr 2024 20:22:04 +0300 Subject: [PATCH 652/826] [Mecha Munch Management]: Fix hint for task #5 (send_to_store) (#3686) Co-authored-by: Dmitrii Cheremushkin --- exercises/concept/mecha-munch-management/.docs/hints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/mecha-munch-management/.docs/hints.md b/exercises/concept/mecha-munch-management/.docs/hints.md index 9edd70abfe..3287768ff5 100644 --- a/exercises/concept/mecha-munch-management/.docs/hints.md +++ b/exercises/concept/mecha-munch-management/.docs/hints.md @@ -38,7 +38,7 @@ The dictionary section of the [official tutorial][dicts-docs] and the mapping ty - What method would you call to get an [iterable view of just the keys][keys] of the dictionary? - Remember that you can get the `value` of a given key by using `[]` syntax. - If you had a `list` or a `tuple`, what [`built-in`][builtins] function might you use to sort them? -- Remember that the `built-in` function can take an optional `reversed=true` argument. +- Remember that the `built-in` function can take an optional `reverse=True` argument. ## 6. Update the Store Inventory to Reflect what a User Has Ordered. From b7e19f0fc8f6b0bbf3e7b3953e223da587e06676 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 23:15:01 -0700 Subject: [PATCH 653/826] Bump actions/checkout from 4.1.3 to 4.1.4 (#3687) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.3 to 4.1.4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/1d96c772d19495a3b5c517cd2bc0cb401ea0529f...0ad4b8fadaa221de15dcec353f45205ec38ea70b) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 4aecd9d346..a029e2eceb 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 28be0f2a35..8878d8e0c2 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index e0ea3ca322..38ad82fdb0 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - name: Run test-runner run: docker-compose run test-runner From 840f4012969ef4793c44ff7cb4b7d95e3d5ce041 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 30 Apr 2024 18:31:59 +0200 Subject: [PATCH 654/826] Sync the `yacht` exercise's docs with the latest data. (#3688) --- .../practice/yacht/.docs/instructions.md | 21 +++++++------------ .../practice/yacht/.docs/introduction.md | 11 ++++++++++ 2 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/yacht/.docs/introduction.md diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md index 54fdb452f5..519b7a68b8 100644 --- a/exercises/practice/yacht/.docs/instructions.md +++ b/exercises/practice/yacht/.docs/instructions.md @@ -1,8 +1,12 @@ # Instructions -The dice game [Yacht][yacht] is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. -In the game, five dice are rolled and the result can be entered in any of twelve categories. -The score of a throw of the dice depends on category chosen. +Given five dice and a category, calculate the score of the dice for that category. + +~~~~exercism/note +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 @@ -21,15 +25,6 @@ The score of a throw of the dice depends on category chosen. | 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 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. - -## Task - -Given a list of values for five dice and a category, your solution should return the score of the dice for that category. -If the dice do not satisfy the requirements of the category your solution should return 0. -You can assume that five values will always be presented, and the value of each will be between one and six inclusively. -You should not assume that the dice are ordered. - -[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) diff --git a/exercises/practice/yacht/.docs/introduction.md b/exercises/practice/yacht/.docs/introduction.md new file mode 100644 index 0000000000..5b541f5625 --- /dev/null +++ b/exercises/practice/yacht/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +Each year, something new is "all the rage" in your high school. +This year it is a dice game: [Yacht][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. + +[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) From 2998cda3cd671a9095f60daf99ad08ba55717a0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 10:40:15 +0200 Subject: [PATCH 655/826] Bump actions/checkout from 4.1.4 to 4.1.5 (#3691) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/0ad4b8fadaa221de15dcec353f45205ec38ea70b...44c2b7a8a4ea60a981eaca3cf939b5f4305c123b) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index a029e2eceb..3268c16860 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 8878d8e0c2..ba6f296bbb 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 38ad82fdb0..e191a97046 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - name: Run test-runner run: docker-compose run test-runner From 4688bf8f1ee46a53190df13b91ce2719cd6e82fe Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 10 May 2024 17:32:46 -0700 Subject: [PATCH 656/826] [Basics Concept/Lasagna Exercise]: Changed `return` keyword Examples (#3689) * Changed examples for use of the return keyword in Python functions. See discussion at http://forum.exercism.org/t/missing-print-in-python-basics-functions-return/11025/4 for additonal information. * Further massaging of examples and adding variable assignment example. --- concepts/basics/about.md | 45 ++++++++++++++++--- concepts/basics/introduction.md | 45 ++++++++++++++++--- .../.docs/introduction.md | 43 +++++++++++++++--- 3 files changed, 113 insertions(+), 20 deletions(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index 6a6fca716b..dd636219ab 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -109,7 +109,7 @@ Related functions and classes (_with their methods_) can be grouped together in The `def` keyword begins a [function definition][function definition]. Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon. -Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. +Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_: ```python @@ -134,24 +134,55 @@ def add_two_numbers(number_one, number_two): IndentationError: unindent does not match any outer indentation level ``` -Functions _explicitly_ return a value or object via the [`return`][return] keyword. -Functions that do not have an _explicit_ `return` expression will _implicitly_ return [`None`][none]. + +Functions _explicitly_ return a value or object via the [`return`][return] keyword: + ```python -# Function definition on first line. +# Function definition on first line, explicit return used on final line. def add_two_numbers(number_one, number_two): - result = number_one + number_two - return result # Returns the sum of the numbers. + return number_one + number_two + +# Calling the function in the Python terminal returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -# This function will return None. +# Assigning the function call to a variable and printing +# the variable will also return the value. +>>> sum_with_return = add_two_numbers(5, 6) +>>> print(sum_with_return) +7 +``` + +Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +The details of `None` will be covered in a later exercise. +For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: + + +```python +# This function does not have an explicit return. def add_two_numbers(number_one, number_two): result = number_one + number_two + +# Calling the function in the Python terminal appears +# to not return anything at all. +>>> add_two_numbers(5, 7) +>>> + + +# Using print() with the function call shows that +# the function is actually returning the **None** object. >>> print(add_two_numbers(5, 7)) None + + +# Assigning the function call to a variable and printing +# the variable will also show None. +>>> sum_without_return = add_two_numbers(5, 6) +>>> print(sum_without_return) +None ``` diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index 34e2a8804d..dc062bc70d 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -50,7 +50,7 @@ Statements for the _body_ of the function begin on the line following `def` and ```python -# The body of a function is indented by 2 spaces, & prints the sum of the numbers. +# The body of this function is indented by 2 spaces,& prints the sum of the numbers. def add_two_numbers(number_one, number_two): total = number_one + number_two print(total) @@ -71,24 +71,55 @@ def add_two_numbers(number_one, number_two): IndentationError: unindent does not match any outer indentation level ``` -Functions explicitly return a value or object via the [`return`][return] keyword. -Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none]. + +Functions _explicitly_ return a value or object via the [`return`][return] keyword: + ```python -# Function definition on first line. +# Function definition on first line, explicit return used on final line. def add_two_numbers(number_one, number_two): - result = number_one + number_two - return result # Returns the sum of the numbers. + return number_one + number_two + +# Calling the function in the Python terminal returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -# This function will return None. +# Assigning the function call to a variable and printing +# the variable will also return the value. +>>> sum_with_return = add_two_numbers(5, 6) +>>> print(sum_with_return) +7 +``` + +Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +The details of `None` will be covered in a later exercise. +For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: + + +```python +# This function does not have an explicit return. def add_two_numbers(number_one, number_two): result = number_one + number_two + +# Calling the function in the Python terminal appears +# to not return anything at all. +>>> add_two_numbers(5, 7) +>>> + + +# Using print() with the function call shows that +# the function is actually returning the **None** object. >>> print(add_two_numbers(5, 7)) None + + +# Assigning the function call to a variable and printing +# the variable will also show None. +>>> sum_without_return = add_two_numbers(5, 6) +>>> print(sum_without_return) +None ``` diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index 6c8de4e69f..6c6812f6d8 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -87,24 +87,55 @@ def add_two_numbers(number_one, number_two): IndentationError: unindent does not match any outer indentation level ``` -Functions explicitly return a value or object via the [`return`][return] keyword. -Functions that do not have an _explicit_ `return` expression will _implicitly_ return [`None`][none]. + +Functions _explicitly_ return a value or object via the [`return`][return] keyword: ```python -# Function definition on first line. +# Function definition on first line, explicit return used on final line. def add_two_numbers(number_one, number_two): - result = number_one + number_two - return result # Returns the sum of the numbers. + return number_one + number_two + +# Calling the function in the Python terminal returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -# This function will return None. +# Assigning the function call to a variable and printing +# the variable will also return the value. +>>> sum_with_return = add_two_numbers(5, 6) +>>> print(sum_with_return) +7 +``` + + +Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +The details of `None` will be covered in a later exercise. +For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: + + +```python +# This function does not have an explicit return. def add_two_numbers(number_one, number_two): result = number_one + number_two + +# Calling the function in the Python terminal appears +# to not return anything at all. +>>> add_two_numbers(5, 7) +>>> + + +# Using print() with the function call shows that +# the function is actually returning the **None** object. >>> print(add_two_numbers(5, 7)) None + + +# Assigning the function call to a variable and printing +# the variable will also show None. +>>> sum_without_return = add_two_numbers(5, 6) +>>> print(sum_without_return) +None ``` From 35ec95423683fded6a6f4a89b6782ea27dfa0e45 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 15 May 2024 20:52:12 +0200 Subject: [PATCH 657/826] Sync the `pig-latin` exercise's docs with the latest data. (#3694) --- .../practice/pig-latin/.docs/instructions.md | 18 ++++-------------- .../practice/pig-latin/.docs/introduction.md | 8 ++++++++ 2 files changed, 12 insertions(+), 14 deletions(-) create mode 100644 exercises/practice/pig-latin/.docs/introduction.md diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 032905aa9b..571708814c 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -1,20 +1,10 @@ # Instructions -Implement a program that translates from English to Pig Latin. +Your task is to translate text from English to Pig Latin using the following rules: -Pig Latin is a made-up children's language that's intended to be confusing. -It obeys a few simple rules (below), but when it's spoken quickly it's really difficult for non-children (and non-native speakers) to understand. - -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. +- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word (e.g. "apple" -> "appleay"). Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. +- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word (e.g. "pig" -> "igpay"). Consonant sounds can be made up of multiple consonants, such as the "ch" in "chair" or "st" in "stand" (e.g. "chair" -> "airchay"). -- **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). +- **Rule 3**: If a word starts with a consonant sound followed by "qu", move them to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). - **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). - -There are a few more rules for edge cases, and there are regional variants too. -Check the tests for all the details. - -Read more about [Pig Latin on Wikipedia][pig-latin]. - -[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin diff --git a/exercises/practice/pig-latin/.docs/introduction.md b/exercises/practice/pig-latin/.docs/introduction.md new file mode 100644 index 0000000000..04baa47586 --- /dev/null +++ b/exercises/practice/pig-latin/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Your parents have challenged you and your sibling to a game of two-on-two basketball. +Confident they'll win, they let you score the first couple of points, but then start taking over the game. +Needing a little boost, you start speaking in [Pig Latin][pig-latin], which is a made-up children's language that's difficult for non-children to understand. +This will give you the edge to prevail over your parents! + +[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin From cbd4deb9cb05d115f2f0a858b141e662c2de006d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 17 May 2024 03:56:47 +0200 Subject: [PATCH 658/826] Sync the `spiral-matrix` exercise's docs with the latest data. (#3695) --- .../practice/spiral-matrix/.docs/instructions.md | 2 +- .../practice/spiral-matrix/.docs/introduction.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/spiral-matrix/.docs/introduction.md diff --git a/exercises/practice/spiral-matrix/.docs/instructions.md b/exercises/practice/spiral-matrix/.docs/instructions.md index ba99e12c73..01e8a77f80 100644 --- a/exercises/practice/spiral-matrix/.docs/instructions.md +++ b/exercises/practice/spiral-matrix/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Given the size, return a square matrix of numbers in spiral order. +Your task is to return a square matrix of a given size. The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples: diff --git a/exercises/practice/spiral-matrix/.docs/introduction.md b/exercises/practice/spiral-matrix/.docs/introduction.md new file mode 100644 index 0000000000..25c7eb595a --- /dev/null +++ b/exercises/practice/spiral-matrix/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +In a small village near an ancient forest, there was a legend of a hidden treasure buried deep within the woods. +Despite numerous attempts, no one had ever succeeded in finding it. +This was about to change, however, thanks to a young explorer named Elara. +She had discovered an old document containing instructions on how to locate the treasure. +Using these instructions, Elara was able to draw a map that revealed the path to the treasure. + +To her surprise, the path followed a peculiar clockwise spiral. +It was no wonder no one had been able to find the treasure before! +With the map in hand, Elara embarks on her journey to uncover the hidden treasure. From 2eede521e08dddc0c99435e771f0e0732d0852f0 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 17 May 2024 03:57:22 +0200 Subject: [PATCH 659/826] Sync the `anagram` exercise's docs with the latest data. (#3697) --- exercises/practice/anagram/.docs/instructions.md | 6 +++--- exercises/practice/anagram/.docs/introduction.md | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 exercises/practice/anagram/.docs/introduction.md diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index 7d1c8283ef..a7298485b3 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,9 +1,9 @@ # Instructions -An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. -A word is not its own anagram: for example, `"stop"` is not an anagram of `"stop"`. +Your task is to, given a target word and a set of candidate words, to find the subset of the candidates that are anagrams of the target. -Given a target word and a set of candidate words, this exercise requests the anagram set: the subset of the candidates that are anagrams of the target. +An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. +A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`. diff --git a/exercises/practice/anagram/.docs/introduction.md b/exercises/practice/anagram/.docs/introduction.md new file mode 100644 index 0000000000..1acbdf00b0 --- /dev/null +++ b/exercises/practice/anagram/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +At a garage sale, you find a lovely vintage typewriter at a bargain price! +Excitedly, you rush home, insert a sheet of paper, and start typing away. +However, your excitement wanes when you examine the output: all words are garbled! +For example, it prints "stop" instead of "post" and "least" instead of "stale." +Carefully, you try again, but now it prints "spot" and "slate." +After some experimentation, you find there is a random delay before each letter is printed, which messes up the order. +You now understand why they sold it for so little money! + +You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word. +Pleased with your finding, you spend the rest of the day generating hundreds of anagrams. From 46c5f7d7c28d7a9cae62ffa5edbb7d10d5b77d54 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 17 May 2024 03:58:21 +0200 Subject: [PATCH 660/826] Sync the `knapsack` exercise's docs with the latest data. (#3698) --- .../practice/knapsack/.docs/instructions.md | 20 +++++-------------- .../practice/knapsack/.docs/introduction.md | 8 ++++++++ 2 files changed, 13 insertions(+), 15 deletions(-) create mode 100644 exercises/practice/knapsack/.docs/introduction.md diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index fadcee1b18..3411db9886 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -1,24 +1,15 @@ # Instructions -In this exercise, let's try to solve a classic problem. +Your task is to determine which items to take so that the total value of his selection is maximized, taking into account the knapsack's carrying capacity. -Bob is a thief. -After months of careful planning, he finally manages to crack the security systems of a high-class apartment. - -In front of him are many items, each with a value (v) and weight (w). -Bob, of course, wants to maximize the total value he can get; he would gladly take all of the items if he could. -However, to his horror, he realizes that the knapsack he carries with him can only hold so much weight (W). - -Given a knapsack with a specific carrying capacity (W), help Bob determine the maximum value he can get from the items in the house. -Note that Bob can take only one of each item. - -All values given will be strictly positive. Items will be represented as a list of items. Each item will have a weight and value. +All values given will be strictly positive. +Bob can take only one of each item. For example: -```none +```text Items: [ { "weight": 5, "value": 10 }, { "weight": 4, "value": 40 }, @@ -26,10 +17,9 @@ Items: [ { "weight": 4, "value": 50 } ] -Knapsack Limit: 10 +Knapsack Maximum Weight: 10 ``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. - In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. He cannot get more than 90 as his knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md new file mode 100644 index 0000000000..9b2bed8b4e --- /dev/null +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Bob is a thief. +After months of careful planning, he finally manages to crack the security systems of a fancy store. + +In front of him are many items, each with a value and weight. +Bob would gladly take all of the items, but his knapsack can only hold so much weight. +Bob has to carefully consider which items to take so that the total value of his selection is maximized. From 657edf94f1f2a1d69ae8d19b08473d0d6eb5f5b5 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 17 May 2024 03:58:46 +0200 Subject: [PATCH 661/826] Sync the `kindergarten-garden` exercise's docs with the latest data. (#3699) --- .../kindergarten-garden/.docs/instructions.md | 32 +++++++++---------- .../kindergarten-garden/.docs/introduction.md | 6 ++++ 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 exercises/practice/kindergarten-garden/.docs/introduction.md diff --git a/exercises/practice/kindergarten-garden/.docs/instructions.md b/exercises/practice/kindergarten-garden/.docs/instructions.md index 472ee26f6c..6fe11a58ce 100644 --- a/exercises/practice/kindergarten-garden/.docs/instructions.md +++ b/exercises/practice/kindergarten-garden/.docs/instructions.md @@ -1,16 +1,21 @@ # Instructions -Given a diagram, determine which plants each child in the kindergarten class is -responsible for. +Your task is to, given a diagram, determine which plants each child in the kindergarten class is responsible for. -The kindergarten class is learning about growing plants. -The teacher thought it would be a good idea to give them actual seeds, plant them in actual dirt, and grow actual plants. +There are 12 children in the class: + +- Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, and Larry. + +Four different types of seeds are planted: -They've chosen to grow grass, clover, radishes, and violets. +| Plant | Diagram encoding | +| ------ | ---------------- | +| Grass | G | +| Clover | C | +| Radish | R | +| Violet | V | -To this end, the children have put little cups along the window sills, and -planted one type of plant in each cup, choosing randomly from the available -types of seeds. +Each child gets four cups, two on each row: ```text [window][window][window] @@ -18,16 +23,9 @@ types of seeds. ........................ ``` -There are 12 children in the class: - -- Alice, Bob, Charlie, David, -- Eve, Fred, Ginny, Harriet, -- Ileana, Joseph, Kincaid, and Larry. - -Each child gets 4 cups, two on each row. -Their teacher assigns cups to the children alphabetically by their names. +Their teacher assigns cups to the children alphabetically by their names, which means that Alice comes first and Larry comes last. -The following diagram represents Alice's plants: +Here is an example diagram representing Alice's plants: ```text [window][window][window] diff --git a/exercises/practice/kindergarten-garden/.docs/introduction.md b/exercises/practice/kindergarten-garden/.docs/introduction.md new file mode 100644 index 0000000000..5ad97d23ec --- /dev/null +++ b/exercises/practice/kindergarten-garden/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +The kindergarten class is learning about growing plants. +The teacher thought it would be a good idea to give the class seeds to plant and grow in the dirt. +To this end, the children have put little cups along the window sills and planted one type of plant in each cup. +The children got to pick their favorites from four available types of seeds: grass, clover, radishes, and violets. From 3f106bb185619aa9d06f683488b03fae9c2c5476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 May 2024 18:59:23 -0700 Subject: [PATCH 662/826] Bump actions/checkout from 4.1.5 to 4.1.6 (#3700) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/44c2b7a8a4ea60a981eaca3cf939b5f4305c123b...a5ac7e51b41094c92402da3b24376905380afc29) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 3268c16860..550e5839d2 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index ba6f296bbb..1e86c15654 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index e191a97046..241ea7240f 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - name: Run test-runner run: docker-compose run test-runner From c137fcff42fc30ea241ae60e3ae4f26146383748 Mon Sep 17 00:00:00 2001 From: Thijs De Meester Date: Tue, 21 May 2024 20:58:10 +0200 Subject: [PATCH 663/826] fix semantics to specify a ghost (#3702) --- .../concept/ghost-gobble-arcade-game/.docs/instructions.md | 2 +- exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py | 2 +- exercises/concept/ghost-gobble-arcade-game/arcade_game.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/ghost-gobble-arcade-game/.docs/instructions.md b/exercises/concept/ghost-gobble-arcade-game/.docs/instructions.md index 4b74839564..04b0b51a42 100644 --- a/exercises/concept/ghost-gobble-arcade-game/.docs/instructions.md +++ b/exercises/concept/ghost-gobble-arcade-game/.docs/instructions.md @@ -8,7 +8,7 @@ You have four rules to implement, all related to the game states. ## 1. Define if Pac-Man eats a ghost -Define the `eat_ghost()` function that takes two parameters (_if Pac-Man has a power pellet active_ and _if Pac-Man is touching a ghost_) and returns a Boolean value if Pac-Man is able to eat the ghost. +Define the `eat_ghost()` function that takes two parameters (_if Pac-Man has a power pellet active_ and _if Pac-Man is touching a ghost_) and returns a Boolean value if Pac-Man is able to eat a ghost. The function should return `True` only if Pac-Man has a power pellet active and is touching a ghost. ```python diff --git a/exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py b/exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py index 1adb3e4579..4de10a25d5 100644 --- a/exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py +++ b/exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py @@ -6,7 +6,7 @@ def eat_ghost(power_pellet_active, touching_ghost): :param power_pellet_active: bool - does the player have an active power pellet? :param touching_ghost: bool - is the player touching a ghost? - :return: bool - can the ghost be eaten? + :return: bool - can a ghost be eaten? """ return power_pellet_active and touching_ghost diff --git a/exercises/concept/ghost-gobble-arcade-game/arcade_game.py b/exercises/concept/ghost-gobble-arcade-game/arcade_game.py index c9807d2320..b2848e0c71 100644 --- a/exercises/concept/ghost-gobble-arcade-game/arcade_game.py +++ b/exercises/concept/ghost-gobble-arcade-game/arcade_game.py @@ -6,7 +6,7 @@ def eat_ghost(power_pellet_active, touching_ghost): :param power_pellet_active: bool - does the player have an active power pellet? :param touching_ghost: bool - is the player touching a ghost? - :return: bool - can the ghost be eaten? + :return: bool - can a ghost be eaten? """ pass From 272f1784d3e2664a4a49011fbc543c9d48a02995 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 24 May 2024 10:08:04 +0200 Subject: [PATCH 664/826] Sync the `bank-account` exercise's docs with the latest data. (#3705) --- .../bank-account/.docs/instructions.md | 12 +++++------ .../bank-account/.docs/introduction.md | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 exercises/practice/bank-account/.docs/introduction.md diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md index f536fdbb73..0955520bbf 100644 --- a/exercises/practice/bank-account/.docs/instructions.md +++ b/exercises/practice/bank-account/.docs/instructions.md @@ -1,12 +1,10 @@ # Instructions -Simulate a bank account supporting opening/closing, withdrawals, and deposits of money. -Watch out for concurrent transactions! +Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money. -A bank account can be accessed in multiple ways. -Clients can make deposits and withdrawals using the internet, mobile phones, etc. -Shops can charge against the account. - -Create an account that can be accessed from multiple threads/processes (terminology depends on your programming language). +As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel. +For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there is no [race conditions][wikipedia] between when you read the account balance and set the new balance. It should be possible to close an account; operations against a closed account must fail. + +[wikipedia]: https://en.wikipedia.org/wiki/Race_condition#In_software diff --git a/exercises/practice/bank-account/.docs/introduction.md b/exercises/practice/bank-account/.docs/introduction.md new file mode 100644 index 0000000000..650b5d9c46 --- /dev/null +++ b/exercises/practice/bank-account/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +After years of filling out forms and waiting, you've finally acquired your banking license. +This means you are now officially eligible to open your own bank, hurray! + +Your first priority is to get the IT systems up and running. +After a day of hard work, you can already open and close accounts, as well as handle withdrawals and deposits. + +Since you couldn't be bothered writing tests, you invite some friends to help test the system. +However, after just five minutes, one of your friends claims they've lost money! +While you're confident your code is bug-free, you start looking through the logs to investigate. + +Ah yes, just as you suspected, your friend is at fault! +They shared their test credentials with another friend, and together they conspired to make deposits and withdrawals from the same account _in parallel_. +Who would do such a thing? + +While you argue that it's physically _impossible_ for someone to access their account in parallel, your friend smugly notifies you that the banking rules _require_ you to support this. +Thus, no parallel banking support, no go-live signal. +Sighing, you create a mental note to work on this tomorrow. +This will set your launch date back at _least_ one more day, but well... From b7cefd19e01f3deb887867f523125f73f4f32b92 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 28 May 2024 15:45:53 +0200 Subject: [PATCH 665/826] Sync the `pig-latin` exercise's docs with the latest data. (#3707) --- .../practice/pig-latin/.docs/instructions.md | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 571708814c..6c843080d4 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -1,10 +1,46 @@ # Instructions -Your task is to translate text from English to Pig Latin using the following rules: - -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word (e.g. "apple" -> "appleay"). - Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word (e.g. "pig" -> "igpay"). - Consonant sounds can be made up of multiple consonants, such as the "ch" in "chair" or "st" in "stand" (e.g. "chair" -> "airchay"). -- **Rule 3**: If a word starts with a consonant sound followed by "qu", move them to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). -- **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). +Your task is to translate text from English to Pig Latin. +The translation is defined using four rules, which look at the pattern of vowels and consonants at the beginning of a word. +These rules look at each word's use of vowels and consonants: + +- vowels: the letters `a`, `e`, `i`, `o`, and `u` +- consonants: the other 21 letters of the English alphabet + +## Rule 1 + +If a word begins with a vowel, or starts with `"xr"` or `"yt"`, add an `"ay"` sound to the end of the word. + +For example: + +- `"apple"` -> `"appleay"` (starts with vowel) +- `"xray"` -> `"xrayay"` (starts with `"xr"`) +- `"yttria"` -> `"yttriaay"` (starts with `"yt"`) + +## Rule 2 + +If a word begins with a one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. + +For example: + +- `"pig"` -> `"igp"` -> `"igpay"` (starts with single consonant) +- `"chair"` -> `"airch"` -> `"airchay"` (starts with multiple consonants) +- `"thrush"` -> `"ushthr"` -> `"ushthray"` (starts with multiple consonants) + +## Rule 3 + +If a word starts with zero or more consonants followed by `"qu"`, first move those consonants (if any) and the `"qu"` part to the end of the word, and then add an `"ay"` sound to the end of the word. + +For example: + +- `"quick"` -> `"ickqu"` -> `"ay"` (starts with `"qu"`, no preceding consonants) +- `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") + +## Rule 4 + +If a word starts with one or more consonants followed by `"y"`, first move the consonants preceding the `"y"`to the end of the word, and then add an `"ay"` sound to the end of the word. + +Some examples: + +- `"my"` -> `"ym"` -> `"ymay"` (starts with single consonant followed by `"y"`) +- `"rhythm"` -> `"ythmrh"` -> `"ythmrhay"` (starts with multiple consonants followed by `"y"`) From 1b262177a49daac97c5c1860e960d4eb2a175f04 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Jun 2024 22:33:51 +0200 Subject: [PATCH 666/826] Sync the `meetup` exercise's docs with the latest data. (#3711) --- .../practice/meetup/.docs/instructions.md | 35 +++++-------------- .../practice/meetup/.docs/introduction.md | 29 +++++++++++++++ 2 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 exercises/practice/meetup/.docs/introduction.md diff --git a/exercises/practice/meetup/.docs/instructions.md b/exercises/practice/meetup/.docs/instructions.md index 0694ef583c..000de2fd12 100644 --- a/exercises/practice/meetup/.docs/instructions.md +++ b/exercises/practice/meetup/.docs/instructions.md @@ -1,11 +1,10 @@ # Instructions -Recurring monthly meetups are generally scheduled on the given weekday of a given week each month. -In this exercise you will be given the recurring schedule, along with a month and year, and then asked to find the exact date of the meetup. +Your task is to find the exact date of a meetup, given a month, year, weekday and week. -For example a meetup might be scheduled on the _first Monday_ of every month. -You might then be asked to find the date that this meetup will happen in January 2018. -In other words, you need to determine the date of the first Monday of January 2018. +There are five week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`. + +For example, you might be asked to find the date for the meetup on the first Monday in January 2018 (January 1, 2018). Similarly, you might be asked to find: @@ -13,29 +12,13 @@ Similarly, you might be asked to find: - the teenth Wednesday of May 2020 (May 13, 2020) - the fourth Sunday of July 2021 (July 25, 2021) - the last Thursday of November 2022 (November 24, 2022) +- the teenth Saturday of August 1953 (August 15, 1953) -The descriptors you are expected to process are: `first`, `second`, `third`, `fourth`, `last`, `teenth`. - -Note that descriptor `teenth` is a made-up word. - -It refers to the seven numbers that end in '-teen' in English: 13, 14, 15, 16, 17, 18, and 19. -But general descriptions of dates use ordinal numbers, e.g. the _first_ Monday, the _third_ Tuesday. - -For the numbers ending in '-teen', that becomes: - -- 13th (thirteenth) -- 14th (fourteenth) -- 15th (fifteenth) -- 16th (sixteenth) -- 17th (seventeenth) -- 18th (eighteenth) -- 19th (nineteenth) +## Teenth -So there are seven numbers ending in '-teen'. -And there are also seven weekdays (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday). -Therefore, it is guaranteed that each day of the week (Monday, Tuesday, ...) will have exactly one numbered day ending with "teen" each month. +The teenth week refers to the seven days in a month that end in '-teenth' (13th, 14th, 15th, 16th, 17th, 18th and 19th). -If asked to find the teenth Saturday of August, 1953 (or, alternately the "Saturteenth" of August, 1953), we need to look at the calendar for August 1953: +If asked to find the teenth Saturday of August, 1953, we check its calendar: ```plaintext August 1953 @@ -48,4 +31,4 @@ Su Mo Tu We Th Fr Sa 30 31 ``` -The Saturday that has a number ending in '-teen' is August 15, 1953. +From this we find that the teenth Saturday is August 15, 1953. diff --git a/exercises/practice/meetup/.docs/introduction.md b/exercises/practice/meetup/.docs/introduction.md new file mode 100644 index 0000000000..29170ef1fd --- /dev/null +++ b/exercises/practice/meetup/.docs/introduction.md @@ -0,0 +1,29 @@ +# Introduction + +Every month, your partner meets up with their best friend. +Both of them have very busy schedules, making it challenging to find a suitable date! +Given your own busy schedule, your partner always double-checks potential meetup dates with you: + +- "Can I meet up on the first Friday of next month?" +- "What about the third Wednesday?" +- "Maybe the last Sunday?" + +In this month's call, your partner asked you this question: + +- "I'd like to meet up on the teenth Thursday; is that okay?" + +Confused, you ask what a "teenth" day is. +Your partner explains that a teenth day, a concept they made up, refers to the days in a month that end in '-teenth': + +- 13th (thirteenth) +- 14th (fourteenth) +- 15th (fifteenth) +- 16th (sixteenth) +- 17th (seventeenth) +- 18th (eighteenth) +- 19th (nineteenth) + +As there are also seven weekdays, it is guaranteed that each day of the week has _exactly one_ teenth day each month. + +Now that you understand the concept of a teenth day, you check your calendar. +You don't have anything planned on the teenth Thursday, so you happily confirm the date with your partner. From 367faf4c12c172bbd981d41f4426f1ae2f0b1f49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 06:57:06 -0700 Subject: [PATCH 667/826] Bump actions/checkout from 4.1.6 to 4.1.7 (#3714) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/a5ac7e51b41094c92402da3b24376905380afc29...692973e3d937129bcbf40652eb9f2f61becf3332) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 550e5839d2..71dbc4356d 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 1e86c15654..f8d9227f3c 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 241ea7240f..d57a2d290c 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Run test-runner run: docker-compose run test-runner From be41e19ae2a8242c1c03a2a16b802da20f41081b Mon Sep 17 00:00:00 2001 From: Yuriy Syrovetskiy <63495+cblp@users.noreply.github.com> Date: Thu, 27 Jun 2024 22:58:58 +0300 Subject: [PATCH 668/826] Fix typo (#3720) --- exercises/concept/currency-exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/currency-exchange/exchange.py b/exercises/concept/currency-exchange/exchange.py index c7088f6183..b58df5cbe4 100644 --- a/exercises/concept/currency-exchange/exchange.py +++ b/exercises/concept/currency-exchange/exchange.py @@ -1,4 +1,4 @@ -"""Functions for calculating steps in exchaning currency. +"""Functions for calculating steps in exchanging currency. Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex From f3fdd2e014f6e3114feed22758588108a6228b6e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:56:59 -0700 Subject: [PATCH 669/826] Updated tests.toml and regenerated test file for Custom-Set. (#3728) --- exercises/practice/custom-set/.meta/tests.toml | 3 +++ exercises/practice/custom-set/custom_set_test.py | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/exercises/practice/custom-set/.meta/tests.toml b/exercises/practice/custom-set/.meta/tests.toml index 8c450e0baf..430c139e68 100644 --- a/exercises/practice/custom-set/.meta/tests.toml +++ b/exercises/practice/custom-set/.meta/tests.toml @@ -114,6 +114,9 @@ description = "Difference (or Complement) of a set is a set of all elements that [c5ac673e-d707-4db5-8d69-7082c3a5437e] description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two non-empty sets is a set of elements that are only in the first set" +[20d0a38f-7bb7-4c4a-ac15-90c7392ecf2b] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference removes all duplicates in the first set" + [c45aed16-5494-455a-9033-5d4c93589dc6] description = "Union returns a set of all elements in either set -> union of empty sets is an empty set" diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index edd7656413..80966272ee 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2023-12-27 +# File last updated on 2024-07-08 import unittest @@ -196,6 +196,12 @@ def test_difference_of_two_non_empty_sets_is_a_set_of_elements_that_are_only_in_ expected = CustomSet([1, 3]) self.assertEqual(set1 - set2, expected) + def test_difference_removes_all_duplicates_in_the_first_set(self): + set1 = CustomSet([1, 1]) + set2 = CustomSet([1]) + expected = CustomSet() + self.assertEqual(set1 - set2, expected) + def test_union_of_empty_sets_is_an_empty_set(self): set1 = CustomSet() set2 = CustomSet() From fe28366c297d5d727a4dca7722c62b2245a556dd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:57:17 -0700 Subject: [PATCH 670/826] synced tests.toml and regenerated tests for Protein-Translation. (#3731) --- exercises/practice/protein-translation/.meta/tests.toml | 4 ++++ .../protein-translation/protein_translation_test.py | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/exercises/practice/protein-translation/.meta/tests.toml b/exercises/practice/protein-translation/.meta/tests.toml index c083605251..f9f1381590 100644 --- a/exercises/practice/protein-translation/.meta/tests.toml +++ b/exercises/practice/protein-translation/.meta/tests.toml @@ -88,6 +88,9 @@ description = "Translation stops if STOP codon in middle of three-codon sequence [2c2a2a60-401f-4a80-b977-e0715b23b93d] description = "Translation stops if STOP codon in middle of six-codon sequence" +[f6f92714-769f-4187-9524-e353e8a41a80] +description = "Sequence of two non-STOP codons does not translate to a STOP codon" + [1e75ea2a-f907-4994-ae5c-118632a1cb0f] description = "Non-existing codon can't translate" include = false @@ -95,6 +98,7 @@ include = false [9eac93f3-627a-4c90-8653-6d0a0595bc6f] description = "Unknown amino acids, not part of a codon, can't translate" include = false +reimplements = "1e75ea2a-f907-4994-ae5c-118632a1cb0f" [9d73899f-e68e-4291-b1e2-7bf87c00f024] description = "Incomplete RNA sequence can't translate" diff --git a/exercises/practice/protein-translation/protein_translation_test.py b/exercises/practice/protein-translation/protein_translation_test.py index 91e6324b6e..03a20fa501 100644 --- a/exercises/practice/protein-translation/protein_translation_test.py +++ b/exercises/practice/protein-translation/protein_translation_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-07-08 import unittest @@ -134,3 +134,8 @@ def test_translation_stops_if_stop_codon_in_middle_of_six_codon_sequence(self): value = "UGGUGUUAUUAAUGGUUU" expected = ["Tryptophan", "Cysteine", "Tyrosine"] self.assertEqual(proteins(value), expected) + + def test_sequence_of_two_non_stop_codons_does_not_translate_to_a_stop_codon(self): + value = "AUGAUG" + expected = ["Methionine", "Methionine"] + self.assertEqual(proteins(value), expected) From fd863a259b5ee75ae7a78ccc1928d6f4264cdf8f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:57:59 -0700 Subject: [PATCH 671/826] Synced tests.toml and regenerated test file for Roman-Numerals. (#3730) --- .../practice/roman-numerals/.meta/tests.toml | 33 ++++++++++--------- .../roman-numerals/roman_numerals_test.py | 5 ++- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/exercises/practice/roman-numerals/.meta/tests.toml b/exercises/practice/roman-numerals/.meta/tests.toml index ca142e9f91..709011b552 100644 --- a/exercises/practice/roman-numerals/.meta/tests.toml +++ b/exercises/practice/roman-numerals/.meta/tests.toml @@ -30,6 +30,9 @@ description = "6 is VI" [ff3fb08c-4917-4aab-9f4e-d663491d083d] description = "9 is IX" +[6d1d82d5-bf3e-48af-9139-87d7165ed509] +description = "16 is XVI" + [2bda64ca-7d28-4c56-b08d-16ce65716cf6] description = "27 is XXVII" @@ -42,6 +45,9 @@ description = "49 is XLIX" [d5b283d4-455d-4e68-aacf-add6c4b51915] description = "59 is LIX" +[4465ffd5-34dc-44f3-ada5-56f5007b6dad] +description = "66 is LXVI" + [46b46e5b-24da-4180-bfe2-2ef30b39d0d0] description = "93 is XCIII" @@ -51,38 +57,35 @@ description = "141 is CXLI" [267f0207-3c55-459a-b81d-67cec7a46ed9] description = "163 is CLXIII" +[902ad132-0b4d-40e3-8597-ba5ed611dd8d] +description = "166 is CLXVI" + [cdb06885-4485-4d71-8bfb-c9d0f496b404] description = "402 is CDII" [6b71841d-13b2-46b4-ba97-dec28133ea80] description = "575 is DLXXV" +[dacb84b9-ea1c-4a61-acbb-ce6b36674906] +description = "666 is DCLXVI" + [432de891-7fd6-4748-a7f6-156082eeca2f] description = "911 is CMXI" [e6de6d24-f668-41c0-88d7-889c0254d173] description = "1024 is MXXIV" -[bb550038-d4eb-4be2-a9ce-f21961ac3bc6] -description = "3000 is MMM" - -[6d1d82d5-bf3e-48af-9139-87d7165ed509] -description = "16 is XVI" - -[4465ffd5-34dc-44f3-ada5-56f5007b6dad] -description = "66 is LXVI" - -[902ad132-0b4d-40e3-8597-ba5ed611dd8d] -description = "166 is CLXVI" - -[dacb84b9-ea1c-4a61-acbb-ce6b36674906] -description = "666 is DCLXVI" - [efbe1d6a-9f98-4eb5-82bc-72753e3ac328] description = "1666 is MDCLXVI" +[bb550038-d4eb-4be2-a9ce-f21961ac3bc6] +description = "3000 is MMM" + [3bc4b41c-c2e6-49d9-9142-420691504336] description = "3001 is MMMI" +[2f89cad7-73f6-4d1b-857b-0ef531f68b7e] +description = "3888 is MMMDCCCLXXXVIII" + [4e18e96b-5fbb-43df-a91b-9cb511fe0856] description = "3999 is MMMCMXCIX" diff --git a/exercises/practice/roman-numerals/roman_numerals_test.py b/exercises/practice/roman-numerals/roman_numerals_test.py index d2120f9b15..675bca0ea3 100644 --- a/exercises/practice/roman-numerals/roman_numerals_test.py +++ b/exercises/practice/roman-numerals/roman_numerals_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-07-08 import unittest @@ -85,5 +85,8 @@ def test_3000_is_mmm(self): def test_3001_is_mmmi(self): self.assertEqual(roman(3001), "MMMI") + def test_3888_is_mmmdccclxxxviii(self): + self.assertEqual(roman(3888), "MMMDCCCLXXXVIII") + def test_3999_is_mmmcmxcix(self): self.assertEqual(roman(3999), "MMMCMXCIX") From 8cc210435a0b02252f3707bbcd58f6ababa4fc89 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:58:21 -0700 Subject: [PATCH 672/826] Synced metadata for Yacht. (#3729) --- exercises/practice/yacht/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json index 78c685cb51..352e162cdf 100644 --- a/exercises/practice/yacht/.meta/config.json +++ b/exercises/practice/yacht/.meta/config.json @@ -20,6 +20,6 @@ ] }, "blurb": "Score a single throw of dice in the game Yacht.", - "source": "James Kilfiger, using wikipedia", + "source": "James Kilfiger, using Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)" } From c5ca9e348768ae331d71a709d6bb55ad98af660d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:58:51 -0700 Subject: [PATCH 673/826] [Exercise Sync] Docs and Metadata for Affine-Cipher & Alphametics. (#3721) * Synced docs for Affine-Cipher and Alphametics. * Alphametics metadata file. --- exercises/practice/affine-cipher/.docs/instructions.md | 4 ++-- exercises/practice/alphametics/.docs/instructions.md | 4 +--- exercises/practice/alphametics/.meta/config.json | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 26ce153426..4eff918de7 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -18,10 +18,10 @@ E(x) = (ai + b) mod m Where: -- `i` is the letter's index from `0` to the length of the alphabet - 1 +- `i` is the letter's index from `0` to the length of the alphabet - 1. - `m` is the length of the alphabet. For the Roman alphabet `m` is `26`. -- `a` and `b` are integers which make the encryption key +- `a` and `b` are integers which make up the encryption key. Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). In case `a` is not coprime to `m`, your program should indicate that this is an error. diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md index 649576ec7e..ef2cbb4a71 100644 --- a/exercises/practice/alphametics/.docs/instructions.md +++ b/exercises/practice/alphametics/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Write a function to solve alphametics puzzles. +Given an alphametics puzzle, find the correct solution. [Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers. @@ -26,6 +26,4 @@ This is correct because every letter is replaced by a different number and the w Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero. -Write a function to solve alphametics puzzles. - [alphametics]: https://en.wikipedia.org/wiki/Alphametics diff --git a/exercises/practice/alphametics/.meta/config.json b/exercises/practice/alphametics/.meta/config.json index 38bc5e3a56..ba9badf856 100644 --- a/exercises/practice/alphametics/.meta/config.json +++ b/exercises/practice/alphametics/.meta/config.json @@ -27,5 +27,5 @@ ] }, "test_runner": false, - "blurb": "Write a function to solve alphametics puzzles." + "blurb": "Given an alphametics puzzle, find the correct solution." } From 6ce2795f6c8063fbe5a03458029dcfd3f9651329 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:59:09 -0700 Subject: [PATCH 674/826] Synced docs for Bank-Account and Darts. (#3722) --- exercises/practice/bank-account/.docs/instructions.md | 2 +- exercises/practice/darts/.docs/instructions.md | 4 ++-- exercises/practice/darts/.meta/config.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md index 0955520bbf..7398fbea18 100644 --- a/exercises/practice/bank-account/.docs/instructions.md +++ b/exercises/practice/bank-account/.docs/instructions.md @@ -3,7 +3,7 @@ Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money. As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel. -For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there is no [race conditions][wikipedia] between when you read the account balance and set the new balance. +For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there are no [race conditions][wikipedia] between when you read the account balance and set the new balance. It should be possible to close an account; operations against a closed account must fail. diff --git a/exercises/practice/darts/.docs/instructions.md b/exercises/practice/darts/.docs/instructions.md index 5e57a860af..6518201c77 100644 --- a/exercises/practice/darts/.docs/instructions.md +++ b/exercises/practice/darts/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Write a function that returns the earned points in a single toss of a Darts game. +Calculate the points scored in a single toss of a Darts game. [Darts][darts] is a game where players throw darts at a [target][darts-target]. @@ -16,7 +16,7 @@ In our particular instance of the game, the target rewards 4 different amounts o The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0). -Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point. +Given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), calculate the correct score earned by a dart landing at that point. ## Credit diff --git a/exercises/practice/darts/.meta/config.json b/exercises/practice/darts/.meta/config.json index a306b38c15..cc71df1a7c 100644 --- a/exercises/practice/darts/.meta/config.json +++ b/exercises/practice/darts/.meta/config.json @@ -24,6 +24,6 @@ ".meta/example.py" ] }, - "blurb": "Write a function that returns the earned points in a single toss of a Darts game.", + "blurb": "Calculate the points scored in a single toss of a Darts game.", "source": "Inspired by an exercise created by a professor Della Paolera in Argentina" } From 16adf4eaf34e73f3c7716ad90abcdefab60e23ae Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:59:26 -0700 Subject: [PATCH 675/826] Synced docs for Flatten-array and Go-Counting. (#3723) --- exercises/practice/flatten-array/.docs/instructions.md | 2 +- exercises/practice/go-counting/.docs/instructions.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/flatten-array/.docs/instructions.md b/exercises/practice/flatten-array/.docs/instructions.md index 51bea67909..89dacfa327 100644 --- a/exercises/practice/flatten-array/.docs/instructions.md +++ b/exercises/practice/flatten-array/.docs/instructions.md @@ -2,7 +2,7 @@ Take a nested list and return a single flattened list with all values except nil/null. -The challenge is to write a function that accepts an arbitrarily-deep nested list-like structure and returns a flattened structure without any nil/null values. +The challenge is to take an arbitrarily-deep nested list-like structure and produce a flattened structure without any nil/null values. For example: diff --git a/exercises/practice/go-counting/.docs/instructions.md b/exercises/practice/go-counting/.docs/instructions.md index 15fdab20ba..e4b143f2da 100644 --- a/exercises/practice/go-counting/.docs/instructions.md +++ b/exercises/practice/go-counting/.docs/instructions.md @@ -5,10 +5,10 @@ Count the scored points on a Go board. In the game of go (also known as baduk, igo, cờ vây and wéiqí) points are gained by completely encircling empty intersections with your stones. The encircled intersections of a player are known as its territory. -Write a function that determines the territory of each player. +Calculate the territory of each player. You may assume that any stones that have been stranded in enemy territory have already been taken off the board. -Write a function that determines the territory which includes a specified coordinate. +Determine the territory which includes a specified coordinate. Multiple empty intersections may be encircled at once and for encircling only horizontal and vertical neighbors count. In the following diagram the stones which matter are marked "O" and the stones that don't are marked "I" (ignored). @@ -25,7 +25,7 @@ Empty spaces represent empty intersections. To be more precise an empty intersection is part of a player's territory if all of its neighbors are either stones of that player or empty intersections that are part of that player's territory. -For more information see [wikipedia][go-wikipedia] or [Sensei's Library][go-sensei]. +For more information see [Wikipedia][go-wikipedia] or [Sensei's Library][go-sensei]. [go-wikipedia]: https://en.wikipedia.org/wiki/Go_%28game%29 [go-sensei]: https://senseis.xmp.net/ From 0443b28c4e2c75ca1430e195e04006851789aee6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:59:45 -0700 Subject: [PATCH 676/826] Synced docs for Killer-Sudoku-Helper and Matching-Brackets. (#3724) --- .../.docs/instructions.md | 28 +++++++++++++++++-- .../matching-brackets/.docs/instructions.md | 3 +- .../matching-brackets/.docs/introduction.md | 8 ++++++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 exercises/practice/matching-brackets/.docs/introduction.md diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md index 8bb05a3a77..fdafdca8fb 100644 --- a/exercises/practice/killer-sudoku-helper/.docs/instructions.md +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -20,7 +20,17 @@ In a 3-digit cage with a sum of 7, there is only one valid combination: 124. - 1 + 2 + 4 = 7 - Any other combination that adds up to 7, e.g. 232, would violate the rule of not repeating digits within a cage. -![Sudoku grid, with three killer cages that are marked as grouped together. The first killer cage is in the 3×3 box in the top left corner of the grid. The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 5. The numbers are highlighted in red to indicate a mistake. The second killer cage is in the central 3×3 box of the grid. The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 4. None of the numbers in this cage are highlighted and therefore don't contain any mistakes. The third killer cage follows the outside corner of the central 3×3 box of the grid. It is made up of the following three cells: the top left cell of the cage contains a 2, highlighted in red, and a cage sum of 7. The top right cell of the cage contains a 3. The bottom right cell of the cage contains a 2, highlighted in red. All other cells are empty.][one-solution-img] +![Sudoku grid, with three killer cages that are marked as grouped together. +The first killer cage is in the 3×3 box in the top left corner of the grid. +The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 5. +The numbers are highlighted in red to indicate a mistake. +The second killer cage is in the central 3×3 box of the grid. +The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 4. +None of the numbers in this cage are highlighted and therefore don't contain any mistakes. +The third killer cage follows the outside corner of the central 3×3 box of the grid. +It is made up of the following three cells: the top left cell of the cage contains a 2, highlighted in red, and a cage sum of 7. +The top right cell of the cage contains a 3. +The bottom right cell of the cage contains a 2, highlighted in red. All other cells are empty.][one-solution-img] ## Example 2: Cage with several combinations @@ -31,7 +41,13 @@ In a 2-digit cage with a sum 10, there are 4 possible combinations: - 37 - 46 -![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. Each continguous two rows form a killer cage and are marked as grouped together. From top to bottom: first group is a cell with value 1 and a pencil mark indicating a cage sum of 10, cell with value 9. Second group is a cell with value 2 and a pencil mark of 10, cell with value 8. Third group is a cell with value 3 and a pencil mark of 10, cell with value 7. Fourth group is a cell with value 4 and a pencil mark of 10, cell with value 6. The last cell in the column is empty.][four-solutions-img] +![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. +Each continguous two rows form a killer cage and are marked as grouped together. +From top to bottom: first group is a cell with value 1 and a pencil mark indicating a cage sum of 10, cell with value 9. +Second group is a cell with value 2 and a pencil mark of 10, cell with value 8. +Third group is a cell with value 3 and a pencil mark of 10, cell with value 7. +Fourth group is a cell with value 4 and a pencil mark of 10, cell with value 6. +The last cell in the column is empty.][four-solutions-img] ## Example 3: Cage with several combinations that is restricted @@ -42,7 +58,13 @@ In a 2-digit cage with a sum 10, where the column already contains a 1 and a 4, 19 and 46 are not possible due to the 1 and 4 in the column according to standard Sudoku rules. -![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. The first row contains a 4, the second is empty, and the third contains a 1. The 1 is highlighted in red to indicate a mistake. The last 6 rows in the column form killer cages of two cells each. From top to bottom: first group is a cell with value 2 and a pencil mark indicating a cage sum of 10, cell with value 8. Second group is a cell with value 3 and a pencil mark of 10, cell with value 7. Third group is a cell with value 1, highlighted in red, and a pencil mark of 10, cell with value 9.][not-possible-img] +![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. +The first row contains a 4, the second is empty, and the third contains a 1. +The 1 is highlighted in red to indicate a mistake. +The last 6 rows in the column form killer cages of two cells each. +From top to bottom: first group is a cell with value 2 and a pencil mark indicating a cage sum of 10, cell with value 8. +Second group is a cell with value 3 and a pencil mark of 10, cell with value 7. +Third group is a cell with value 1, highlighted in red, and a pencil mark of 10, cell with value 9.][not-possible-img] ## Trying it yourself diff --git a/exercises/practice/matching-brackets/.docs/instructions.md b/exercises/practice/matching-brackets/.docs/instructions.md index 544daa968d..ea17084232 100644 --- a/exercises/practice/matching-brackets/.docs/instructions.md +++ b/exercises/practice/matching-brackets/.docs/instructions.md @@ -1,4 +1,5 @@ # Instructions Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. -The string may also contain other characters, which for the purposes of this exercise should be ignored. +Any other characters should be ignored. +For example, `"{what is (42)}?"` is balanced and `"[text}"` is not. diff --git a/exercises/practice/matching-brackets/.docs/introduction.md b/exercises/practice/matching-brackets/.docs/introduction.md new file mode 100644 index 0000000000..0618221b21 --- /dev/null +++ b/exercises/practice/matching-brackets/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You're given the opportunity to write software for the Bracketeer™, an ancient but powerful mainframe. +The software that runs on it is written in a proprietary language. +Much of its syntax is familiar, but you notice _lots_ of brackets, braces and parentheses. +Despite the Bracketeer™ being powerful, it lacks flexibility. +If the source code has any unbalanced brackets, braces or parentheses, the Bracketeer™ crashes and must be rebooted. +To avoid such a scenario, you start writing code that can verify that brackets, braces, and parentheses are balanced before attempting to run it on the Bracketeer™. From 222986ec167edb4779f476bb94c7c48eb4fcbfc5 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:00:29 -0700 Subject: [PATCH 677/826] Synced Pascals-Triangle and Pig-Latin docs. (#3725) --- .../pascals-triangle/.docs/instructions.md | 27 ++++++++++++++++--- .../pascals-triangle/.docs/introduction.md | 22 +++++++++++++++ .../practice/pig-latin/.docs/instructions.md | 4 +-- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/pascals-triangle/.docs/introduction.md diff --git a/exercises/practice/pascals-triangle/.docs/instructions.md b/exercises/practice/pascals-triangle/.docs/instructions.md index f556785931..0f58f00696 100644 --- a/exercises/practice/pascals-triangle/.docs/instructions.md +++ b/exercises/practice/pascals-triangle/.docs/instructions.md @@ -1,8 +1,20 @@ # Instructions -Compute Pascal's triangle up to a given number of rows. +Your task is to output the first N rows of Pascal's triangle. -In Pascal's Triangle each number is computed by adding the numbers to the right and left of the current position in the previous row. +[Pascal's triangle][wikipedia] is a triangular array of positive integers. + +In Pascal's triangle, the number of values in a row is equal to its row number (which starts at one). +Therefore, the first row has one value, the second row has two values, and so on. + +The first (topmost) row has a single value: `1`. +Subsequent rows' values are computed by adding the numbers directly to the right and left of the current position in the previous row. + +If the previous row does _not_ have a value to the left or right of the current position (which only happens for the leftmost and rightmost positions), treat that position's value as zero (effectively "ignoring" it in the summation). + +## Example + +Let's look at the first 5 rows of Pascal's Triangle: ```text 1 @@ -10,5 +22,14 @@ In Pascal's Triangle each number is computed by adding the numbers to the right 1 2 1 1 3 3 1 1 4 6 4 1 -# ... etc ``` + +The topmost row has one value, which is `1`. + +The leftmost and rightmost values have only one preceding position to consider, which is the position to its right respectively to its left. +With the topmost value being `1`, it follows from this that all the leftmost and rightmost values are also `1`. + +The other values all have two positions to consider. +For example, the fifth row's (`1 4 6 4 1`) middle value is `6`, as the values to its left and right in the preceding row are `3` and `3`: + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle diff --git a/exercises/practice/pascals-triangle/.docs/introduction.md b/exercises/practice/pascals-triangle/.docs/introduction.md new file mode 100644 index 0000000000..60b8ec30dc --- /dev/null +++ b/exercises/practice/pascals-triangle/.docs/introduction.md @@ -0,0 +1,22 @@ +# Introduction + +With the weather being great, you're not looking forward to spending an hour in a classroom. +Annoyed, you enter the class room, where you notice a strangely satisfying triangle shape on the blackboard. +Whilst waiting for your math teacher to arrive, you can't help but notice some patterns in the triangle: the outer values are all ones, each subsequent row has one more value than its previous row and the triangle is symmetrical. +Weird! + +Not long after you sit down, your teacher enters the room and explains that this triangle is the famous [Pascal's triangle][wikipedia]. + +Over the next hour, your teacher reveals some amazing things hidden in this triangle: + +- It can be used to compute how many ways you can pick K elements from N values. +- It contains the Fibonacci sequence. +- If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. + +The teacher implores you and your classmates to lookup other uses, and assures you that there are lots more! +At that moment, the school bell rings. +You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. +You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle +[wikipedia-sierpinski-triangle]: https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 6c843080d4..a9645ac236 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -19,7 +19,7 @@ For example: ## Rule 2 -If a word begins with a one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. +If a word begins with one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. For example: @@ -33,7 +33,7 @@ If a word starts with zero or more consonants followed by `"qu"`, first move tho For example: -- `"quick"` -> `"ickqu"` -> `"ay"` (starts with `"qu"`, no preceding consonants) +- `"quick"` -> `"ickqu"` -> `"ickquay"` (starts with `"qu"`, no preceding consonants) - `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") ## Rule 4 From de75875626f447d8f488b0f009869855e0bbbdf9 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:00:48 -0700 Subject: [PATCH 678/826] Synced docs for poker and Resistor-color-duo. (#3726) --- exercises/practice/poker/.docs/instructions.md | 2 +- exercises/practice/resistor-color-duo/.docs/instructions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/poker/.docs/instructions.md b/exercises/practice/poker/.docs/instructions.md index 492fc4c9e0..107cd49d66 100644 --- a/exercises/practice/poker/.docs/instructions.md +++ b/exercises/practice/poker/.docs/instructions.md @@ -2,6 +2,6 @@ Pick the best hand(s) from a list of poker hands. -See [wikipedia][poker-hands] for an overview of poker hands. +See [Wikipedia][poker-hands] for an overview of poker hands. [poker-hands]: https://en.wikipedia.org/wiki/List_of_poker_hands diff --git a/exercises/practice/resistor-color-duo/.docs/instructions.md b/exercises/practice/resistor-color-duo/.docs/instructions.md index bdcd549b1a..18ee4078bb 100644 --- a/exercises/practice/resistor-color-duo/.docs/instructions.md +++ b/exercises/practice/resistor-color-duo/.docs/instructions.md @@ -29,5 +29,5 @@ The band colors are encoded as follows: - White: 9 From the example above: -brown-green should return 15 +brown-green should return 15, and brown-green-violet should return 15 too, ignoring the third color. From 5e4dc23cd665b5146f31edad967cab9d1508d3a3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:01:05 -0700 Subject: [PATCH 679/826] Synced docs for Space-Age and Zebra-Puzzle. (#3727) --- .../practice/space-age/.docs/instructions.md | 31 ++++++++++--------- .../practice/space-age/.docs/introduction.md | 20 ++++++++++++ .../zebra-puzzle/.docs/instructions.md | 18 +++++------ .../zebra-puzzle/.docs/introduction.md | 2 +- 4 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 exercises/practice/space-age/.docs/introduction.md diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md index fe938cc09e..f23b5e2c1f 100644 --- a/exercises/practice/space-age/.docs/instructions.md +++ b/exercises/practice/space-age/.docs/instructions.md @@ -1,25 +1,28 @@ # Instructions -Given an age in seconds, calculate how old someone would be on: +Given an age in seconds, calculate how old someone would be on a planet in our Solar System. -- Mercury: orbital period 0.2408467 Earth years -- Venus: orbital period 0.61519726 Earth years -- Earth: orbital period 1.0 Earth years, 365.25 Earth days, or 31557600 seconds -- Mars: orbital period 1.8808158 Earth years -- Jupiter: orbital period 11.862615 Earth years -- Saturn: orbital period 29.447498 Earth years -- Uranus: orbital period 84.016846 Earth years -- Neptune: orbital period 164.79132 Earth years +One Earth year equals 365.25 Earth days, or 31,557,600 seconds. +If you were told someone was 1,000,000,000 seconds old, their age would be 31.69 Earth-years. -So if you were told someone were 1,000,000,000 seconds old, you should -be able to say that they're 31.69 Earth-years old. +For the other planets, you have to account for their orbital period in Earth Years: -If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. +| Planet | Orbital period in Earth Years | +| ------- | ----------------------------- | +| Mercury | 0.2408467 | +| Venus | 0.61519726 | +| Earth | 1.0 | +| Mars | 1.8808158 | +| Jupiter | 11.862615 | +| Saturn | 29.447498 | +| Uranus | 84.016846 | +| Neptune | 164.79132 | -Note: The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). +~~~~exercism/note +The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). The Gregorian calendar has, on average, 365.2425 days. While not entirely accurate, 365.25 is the value used in this exercise. See [Year on Wikipedia][year] for more ways to measure a year. -[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs [year]: https://en.wikipedia.org/wiki/Year#Summary +~~~~ diff --git a/exercises/practice/space-age/.docs/introduction.md b/exercises/practice/space-age/.docs/introduction.md new file mode 100644 index 0000000000..014d78857c --- /dev/null +++ b/exercises/practice/space-age/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +The year is 2525 and you've just embarked on a journey to visit all planets in the Solar System (Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune). +The first stop is Mercury, where customs require you to fill out a form (bureaucracy is apparently _not_ Earth-specific). +As you hand over the form to the customs officer, they scrutinize it and frown. +"Do you _really_ expect me to believe you're just 50 years old? +You must be closer to 200 years old!" + +Amused, you wait for the customs officer to start laughing, but they appear to be dead serious. +You realize that you've entered your age in _Earth years_, but the officer expected it in _Mercury years_! +As Mercury's orbital period around the sun is significantly shorter than Earth, you're actually a lot older in Mercury years. +After some quick calculations, you're able to provide your age in Mercury Years. +The customs officer smiles, satisfied, and waves you through. +You make a mental note to pre-calculate your planet-specific age _before_ future customs checks, to avoid such mix-ups. + +~~~~exercism/note +If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. + +[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs +~~~~ diff --git a/exercises/practice/zebra-puzzle/.docs/instructions.md b/exercises/practice/zebra-puzzle/.docs/instructions.md index c666e33cb3..aedce9b25e 100644 --- a/exercises/practice/zebra-puzzle/.docs/instructions.md +++ b/exercises/practice/zebra-puzzle/.docs/instructions.md @@ -12,20 +12,20 @@ The following 15 statements are all known to be true: 1. There are five houses. 2. The Englishman lives in the red house. 3. The Spaniard owns the dog. -4. Coffee is drunk in the green house. +4. The person in the green house drinks coffee. 5. The Ukrainian drinks tea. 6. The green house is immediately to the right of the ivory house. -7. The Old Gold smoker owns snails. -8. Kools are smoked in the yellow house. -9. Milk is drunk in the middle house. +7. The snail owner likes to go dancing. +8. The person in the yellow house is a painter. +9. The person in the middle house drinks milk. 10. The Norwegian lives in the first house. -11. The man who smokes Chesterfields lives in the house next to the man with the fox. -12. Kools are smoked in the house next to the house where the horse is kept. -13. The Lucky Strike smoker drinks orange juice. -14. The Japanese smokes Parliaments. +11. The person who enjoys reading lives in the house next to the person with the fox. +12. The painter's house is next to the house with the horse. +13. The person who plays football drinks orange juice. +14. The Japanese person plays chess. 15. The Norwegian lives next to the blue house. -Additionally, each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and smoke different brands of cigarettes. +Additionally, each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and engage in different hobbies. ~~~~exercism/note There are 24 billion (5!⁵ = 24,883,200,000) possible solutions, so try ruling out as many solutions as possible. diff --git a/exercises/practice/zebra-puzzle/.docs/introduction.md b/exercises/practice/zebra-puzzle/.docs/introduction.md index 33d688fd51..bbcaa6fd20 100644 --- a/exercises/practice/zebra-puzzle/.docs/introduction.md +++ b/exercises/practice/zebra-puzzle/.docs/introduction.md @@ -1,7 +1,7 @@ # Introduction The Zebra Puzzle is a famous logic puzzle in which there are five houses, each painted a different color. -The houses have different inhabitants, who have different nationalities, own different pets, drink different beverages and smoke different brands of cigarettes. +The houses have different inhabitants, who have different nationalities, own different pets, drink different beverages and enjoy different hobbies. To help you solve the puzzle, you're given 15 statements describing the solution. However, only by combining the information in _all_ statements will you be able to find the solution to the puzzle. From e407e5df7871cdcfa48f48b29e158199fb1f4d4e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:34:05 -0700 Subject: [PATCH 680/826] [Guido's Gorgeous Lasagna] Update introduction.md (#3732) Corrected example/sum on line 107 to 11. --- exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index 6c6812f6d8..c3fe37541e 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -104,7 +104,7 @@ def add_two_numbers(number_one, number_two): # the variable will also return the value. >>> sum_with_return = add_two_numbers(5, 6) >>> print(sum_with_return) -7 +11 ``` From c7843f557d4f3b3faf622aef7a0e5764bb8dc20b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:35:56 -0700 Subject: [PATCH 681/826] [Basics] Update about.md (#3733) Corrected sum in example on line 155. --- concepts/basics/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index dd636219ab..ef873ce418 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -152,7 +152,7 @@ def add_two_numbers(number_one, number_two): # the variable will also return the value. >>> sum_with_return = add_two_numbers(5, 6) >>> print(sum_with_return) -7 +11 ``` Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. From 47e7837b61d2144bc1e212381b49642e3326ce7e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 16:05:19 -0700 Subject: [PATCH 682/826] [POV] Update instructions.append.md & Remove Error Message Examples (#3734) --- exercises/practice/pov/.docs/instructions.append.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/exercises/practice/pov/.docs/instructions.append.md b/exercises/practice/pov/.docs/instructions.append.md index 83ab12475c..024842736a 100644 --- a/exercises/practice/pov/.docs/instructions.append.md +++ b/exercises/practice/pov/.docs/instructions.append.md @@ -6,12 +6,4 @@ Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tuto This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" multiple `ValueErrors` if the `Tree()` class is passed a tree that cannot be reoriented, or a path cannot be found between a `start node` and an `end node`. The tests will only pass if you both `raise` the `exception` and include a message with it. -To raise a `ValueError` with a message, write the message as an argument to the `exception` type: - -```python -# when a tree cannot be oriented to a new node POV -raise ValueError("Tree could not be reoriented") - -#when a path cannot be found between a start and end node on the tree. -raise ValueError("No path found") -``` \ No newline at end of file +Please check the tests and their expected results carefully. From c3278e145868815f4dd257aab268d1adddbf3167 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 16:20:26 -0700 Subject: [PATCH 683/826] [Update DnD Character] Add Missing Function to Stub (#3735) Per [this discussion](https://forum.exercism.org/t/d-d-character-test-bug/11237/3) on the forum. Decided to amend the stub, as it was the easiest an least confusing solution. --- exercises/practice/dnd-character/dnd_character.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/exercises/practice/dnd-character/dnd_character.py b/exercises/practice/dnd-character/dnd_character.py index f8ecd30bb8..1d310dde81 100644 --- a/exercises/practice/dnd-character/dnd_character.py +++ b/exercises/practice/dnd-character/dnd_character.py @@ -1,3 +1,6 @@ class Character: def __init__(self): pass + +def modifier(value): + pass From bce0ee289babe55cd3846beb44ce41506c42dfb7 Mon Sep 17 00:00:00 2001 From: qadzek <84473512+qadzek@users.noreply.github.com> Date: Wed, 10 Jul 2024 00:55:36 +0200 Subject: [PATCH 684/826] [Lists] Fix typo (#3737) --- concepts/lists/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/lists/about.md b/concepts/lists/about.md index 657cd43ff6..f7d4054eef 100644 --- a/concepts/lists/about.md +++ b/concepts/lists/about.md @@ -296,7 +296,7 @@ Assigning a `list` object to a new variable _name_ **does not copy the `list` ob Any change made to the elements in the `list` under the _new_ name _impact the original_. -Making a `shallow_copy` via `list.copy()` or slice will avoid this first-leve referencing complication. +Making a `shallow_copy` via `list.copy()` or slice will avoid this first-level referencing complication. A `shallow_copy` will create a new `list` object, but **will not** create new objects for the contained list _elements_. This type of copy will usually be enough for you to add or remove items from the two `list` objects independently, and effectively have two "separate" lists. From 030fcca1983e628ff14b9a22c646fd26e220c7fb Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Wed, 10 Jul 2024 19:58:20 +0100 Subject: [PATCH 685/826] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3736)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/0328994b105cecbf8d5bcab2a7fc5b9791685f87 --- bin/fetch-configlet | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/bin/fetch-configlet b/bin/fetch-configlet index 4800e15084..6bef43ab72 100755 --- a/bin/fetch-configlet +++ b/bin/fetch-configlet @@ -24,10 +24,11 @@ get_download_url() { local latest='https://api.github.com/repos/exercism/configlet/releases/latest' local arch case "$(uname -m)" in - x86_64) arch='x86-64' ;; - *686*) arch='i386' ;; - *386*) arch='i386' ;; - *) arch='x86-64' ;; + aarch64|arm64) arch='arm64' ;; + x86_64) arch='x86-64' ;; + *686*) arch='i386' ;; + *386*) arch='i386' ;; + *) arch='x86-64' ;; esac local suffix="${os}_${arch}.${ext}" curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" | @@ -47,7 +48,7 @@ main() { fi local os - case "$(uname)" in + case "$(uname -s)" in Darwin*) os='macos' ;; Linux*) os='linux' ;; Windows*) os='windows' ;; @@ -58,8 +59,8 @@ main() { local ext case "${os}" in - windows*) ext='zip' ;; - *) ext='tar.gz' ;; + windows) ext='zip' ;; + *) ext='tar.gz' ;; esac echo "Fetching configlet..." >&2 @@ -69,16 +70,16 @@ main() { curl "${curlopts[@]}" --output "${output_path}" "${download_url}" case "${ext}" in - *zip) unzip "${output_path}" -d "${output_dir}" ;; - *) tar xzf "${output_path}" -C "${output_dir}" ;; + zip) unzip "${output_path}" -d "${output_dir}" ;; + *) tar xzf "${output_path}" -C "${output_dir}" ;; esac rm -f "${output_path}" local executable_ext case "${os}" in - windows*) executable_ext='.exe' ;; - *) executable_ext='' ;; + windows) executable_ext='.exe' ;; + *) executable_ext='' ;; esac local configlet_path="${output_dir}/configlet${executable_ext}" From a5159b5c88b938f73d93f7c54474563e3856b50d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 11 Jul 2024 22:32:23 +0200 Subject: [PATCH 686/826] Sync the `resistor-color` exercise's docs with the latest data. (#3739) --- .../resistor-color/.docs/instructions.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/resistor-color/.docs/instructions.md b/exercises/practice/resistor-color/.docs/instructions.md index 646c14398f..0125e718b4 100644 --- a/exercises/practice/resistor-color/.docs/instructions.md +++ b/exercises/practice/resistor-color/.docs/instructions.md @@ -15,16 +15,16 @@ In this exercise you are going to create a helpful program so that you don't hav These colors are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 The goal of this exercise is to create a way: From 5c9131f9e2ed6e9b7eeb1af633fada9f09882a5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:33:04 -0700 Subject: [PATCH 687/826] Bump actions/setup-python from 5.1.0 to 5.1.1 (#3738) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.1.0 to 5.1.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/82c7e631bb3cdc910f68e0081d67478d79c6982d...39cd14951b08e74b54015e9e001cdefcf80e669f) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 71dbc4356d..72be71f702 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Set up Python - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d + uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f with: python-version: ${{ matrix.python-version }} From 2529efc02f7ca53c532ccaba8d1ee7041608d462 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 11 Jul 2024 22:33:33 +0200 Subject: [PATCH 688/826] Sync the `resistor-color-duo` exercise's docs with the latest data. (#3740) --- .../resistor-color-duo/.docs/instructions.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/resistor-color-duo/.docs/instructions.md b/exercises/practice/resistor-color-duo/.docs/instructions.md index 18ee4078bb..4ae694da02 100644 --- a/exercises/practice/resistor-color-duo/.docs/instructions.md +++ b/exercises/practice/resistor-color-duo/.docs/instructions.md @@ -17,16 +17,16 @@ The program will take color names as input and output a two digit number, even i The band colors are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 From the example above: brown-green should return 15, and From f4e036aa05dd436fe2e897d351ce990f718f9f25 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 11 Jul 2024 22:33:58 +0200 Subject: [PATCH 689/826] Sync the `resistor-color-trio` exercise's docs with the latest data. (#3741) --- .../resistor-color-trio/.docs/instructions.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/resistor-color-trio/.docs/instructions.md b/exercises/practice/resistor-color-trio/.docs/instructions.md index 59d22783b9..1ac5cf5e9f 100644 --- a/exercises/practice/resistor-color-trio/.docs/instructions.md +++ b/exercises/practice/resistor-color-trio/.docs/instructions.md @@ -12,16 +12,16 @@ For this exercise, you need to know only three things about them: The program will take 3 colors as input, and outputs the correct value, in ohms. The color bands are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 In Resistor Color Duo you decoded the first two colors. For instance: orange-orange got the main value `33`. From a47255b9bba08739a8752e5f1c88b2cc0f5f49c3 Mon Sep 17 00:00:00 2001 From: qadzek <84473512+qadzek@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:37:56 +0200 Subject: [PATCH 690/826] [Pythagorean Triplet] Fix typos (#3742) --- .../pythagorean-triplet/.approaches/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/pythagorean-triplet/.approaches/introduction.md b/exercises/practice/pythagorean-triplet/.approaches/introduction.md index ab79ef1afd..0a8cb17141 100644 --- a/exercises/practice/pythagorean-triplet/.approaches/introduction.md +++ b/exercises/practice/pythagorean-triplet/.approaches/introduction.md @@ -6,7 +6,7 @@ The problem space can easily become very large, and 'naive' or more 'brute force There are three reasonably common variations to this problem 1. A [cubic time][approaches-cubic] solution, which uses highly nested loops and is non-performant. 2. A [quadratic time][approaches-quadratic] solution, which uses one nested loop, and is reasonably easy to figure out. -3. A [linear time][approaches-linear] solution, requiring some deeper understanding of the mathematics of finding trplets. +3. A [linear time][approaches-linear] solution, requiring some deeper understanding of the mathematics of finding triplets. If those terms are unclear to you, you might like to read about [time complexity][time-complexity], and how it is described by [asymptotic notation][asymptotic-notation]. @@ -68,7 +68,7 @@ This gives a substantial speed advantage, allowing the tests to run to completio However, the Exercism online test runner will still time out with this solution. -Examining the code, it is clear that the upper bounds on the `loop` variables are far too generous, and too much work is bing done. +Examining the code, it is clear that the upper bounds on the `loop` variables are far too generous, and too much work is being done. The solution below tightens the bounds and pre-calculates `c * c` in the outer `loop`. @@ -171,7 +171,7 @@ def triplets_with_sum(number): If we could be sure that the code only had to handle small values of `n`, a quadratic method would have the advantage of clarity. However, the test suite goes up to 30_000, and the online test runner quickly times out. -We therefor need to accept some less readable code and use a linear-time implementation. +We therefore need to accept some less readable code and use a linear-time implementation. Full details of run-time benchmarking are given in the [Performance article][article-performance]. From c1fd7667575c6d677ddd9b4ecbe28175979e25f4 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 14 Jul 2024 11:37:58 -0700 Subject: [PATCH 691/826] Re-ordered Python docs in prep for new additions. (#3743) --- docs/config.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/config.json b/docs/config.json index ccaea6ac24..8474ac07ad 100644 --- a/docs/config.json +++ b/docs/config.json @@ -14,20 +14,6 @@ "title": "How to learn Python", "blurb": "An overview of how to get started from scratch with Python." }, - { - "uuid": "7a2e1a4f-1fa8-4327-b700-5af101fcdc89", - "slug": "test-driven-development", - "path": "docs/TDD.md", - "title": "Test Driven Development", - "blurb": "An overview of Test Driven Development." - }, - { - "uuid": "3829fdff-47ac-4283-ae47-a5db1dbce956", - "slug": "traceback-reading", - "path": "docs/TRACEBACKS.md", - "title": "How to read tracebacks", - "blurb": "An overview of how to read Python tracebacks for debugging." - }, { "uuid": "8666f259-de7d-4928-ae6f-15ff6fe6bb74", "slug": "tests", @@ -35,6 +21,13 @@ "title": "Testing on the Python track", "blurb": "Learn how to test your Python exercises on Exercism." }, + { + "uuid": "7a2e1a4f-1fa8-4327-b700-5af101fcdc89", + "slug": "test-driven-development", + "path": "docs/TDD.md", + "title": "Test Driven Development", + "blurb": "An overview of Test Driven Development." + }, { "uuid": "f18d3af2-fb71-41c6-984a-32b3ba86bf02", "slug": "problem-solving", @@ -42,6 +35,13 @@ "title": "Problem Solving Resources", "blurb": "Learn some general problem-solving techniques to help you with programming." }, + { + "uuid": "3829fdff-47ac-4283-ae47-a5db1dbce956", + "slug": "traceback-reading", + "path": "docs/TRACEBACKS.md", + "title": "How to read tracebacks", + "blurb": "An overview of how to read Python tracebacks for debugging." + }, { "uuid": "73ced51d-76d0-45af-952c-8a6d7b5f3f7a", "slug": "resources", From 9d7e0a531a056273947d0d65412c30ab51a37c31 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 14 Jul 2024 11:39:47 -0700 Subject: [PATCH 692/826] Changed Pascals to Pascal's for the Exercise name. (#3744) --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 99d23d77c3..38dd7520e3 100644 --- a/config.json +++ b/config.json @@ -1554,7 +1554,7 @@ }, { "slug": "pascals-triangle", - "name": "Pascals Triangle", + "name": "Pascal's Triangle", "uuid": "e1e1c7d7-c1d9-4027-b90d-fad573182419", "practices": ["recursion"], "prerequisites": [ From 20437121e4e991865033fc4da30dc5da84941680 Mon Sep 17 00:00:00 2001 From: Kelly Young Date: Wed, 24 Jul 2024 13:30:42 -0500 Subject: [PATCH 693/826] (fix): update pylint tutorial link and link text (#3748) --- docs/TESTS.md | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 242555371f..54a10b5782 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -10,8 +10,7 @@ You should also install the following `pytest` plugins: We also recommend using the code linting program [pylint][pylint], as it is part of our automated feedback on the website and can be a very useful static code analysis tool. For ease-of-use, the [pytest-pylint][pytest-pylint] plugin for `pytest` will allow you to run `pylint` via `pytest` on the command line. -Pylint configuration can be a bit much, so this [tutorial from pycqa.org][tutorial from pycqa.org] can be helpful for getting started, as can this overview of [Code Quality: Tools and Best Practices][Code Quality: Tools and Best Practices] from Real Python. - +Pylint configuration can be a bit much, so this [tutorial from pylint.readthedocs.io][tutorial from pylint.readthedocs.io] can be helpful for getting started, as can this overview of [Code Quality: Tools and Best Practices][Code Quality: Tools and Best Practices] from Real Python. ## Installing pytest @@ -25,7 +24,6 @@ Please adjust the install commands below accordingly. To install `pytest` in a virtual environment, ensure the environment **is activated** prior to executing commands. Otherwise, the `pytest` installation will be global. - #### Windows ```powershell @@ -41,7 +39,6 @@ Successfully installed pytest-7.2.2 ... ``` - To check if installation was successful: ```bash @@ -85,7 +82,6 @@ More information on pytest marks can be found in the `pytest` documentation on [ _More information on customizing pytest configurations can be found in the pytest documentation on [configuration file formats][configuration file formats]_ - ### Test Failures When tests fail, `pytest` prints the text of each failed test, along with the expected and actual `return` values of each to the terminal. @@ -110,13 +106,12 @@ FAILED exercise_test.py::ExerciseTest::name_of_failed_test If you really want to be specific about what pytest returns on your screen, here are some handy command-line arguments that allows you to configure its behavior. - #### Return All Details [`-v`] Adding the `-v` (_verbose_) flag will return both environment information and a test summary in addition to test failures: ```bash -$(my_venv) python3 -m pytest -o markers=task -v exercises// +$(my_venv) python3 -m pytest -o markers=task -v exercises// ======================================== test session starts =========================================== platform darwin -- Python 3.9.0, pytest-6.2.5, -- /usr/local/envs/my_env/bin/python3 @@ -125,7 +120,7 @@ metadata: {'Python': '3.9.0', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Pa rootdir: /Users//exercism/python, configfile: pytest.ini plugins: subtests-0.5.0, pylint-0.18.0 -collected 5 items +collected 5 items exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_one FAILED [ 20%] exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_two FAILED @@ -149,7 +144,7 @@ Using the `-x` flag will run the tests as normal, but stop the test run upon the This helps when you want to debug a single task or test failure at a time: ```bash -$(my_venv) python3 -m pytest -o markers=task -x exercises// +$(my_venv) python3 -m pytest -o markers=task -x exercises// =================== FAILURES ==================== _______________ example_test_foo ________________ @@ -166,7 +161,6 @@ FAILED example_test.py::ExampleTest::example_test_foo The `pytest-cache` plugin remembers which tests failed last time you ran `pytest`, so using the flag `--ff` will tell `pytest` to run previously failed tests **first**, then continue with the remainder of the tests. This might speed up your testing if you are making a lot of smaller fixes around one particular task or set of inputs. - ```bash $(my_venv) python3 -m pytest -o markers=task --ff ==================== 7 passed in 503s ==================== @@ -192,7 +186,6 @@ This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. When you make fixes and run the test again, `pytest` will first run the previous test that failed, then continue with the remaining tests. - ### Using PDB, the Python Debugger, with pytest If you want to "debug like a pro", you can use the `--pdb` argument after the `pytest` command, and drop into the built-in [Python debugger][pdb], `PDB`. @@ -206,13 +199,11 @@ When a test fails, dropping into `PDB` will allow you to step through your code More details on the `PDB` module can be found in the [Python documentation on PDB][pdb]. Additionally, the [pytest docs on PDB][pytest-pdb] and [this guide from Real Python](https://realpython.com/python-debugging-pdb/) are extremely helpful. - ## Extending your IDE If you'd like to extend your IDE with some tools that will help you with testing and improving your code, check the [tools](./tools) page. We explore multiple IDEs, editors and some useful extensions for linting and debugging there. - ## Additional information ### Adding python to your PATH @@ -245,7 +236,6 @@ Then add a new line, as shown in the picture, replacing `{python_directory}` wit ![Add python to path](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-AddPythonPath.png) - #### MacOS/Linux The below should work for most Linux and MacOS flavors with a `bash` shell. @@ -264,13 +254,13 @@ export PATH=”$PATH:{python_directory}}” [pip]: https://pip.pypa.io/en/stable/getting-started/ [psf-installer]: https://www.python.org/downloads/ [pylint]: https://pylint.pycqa.org/en/latest/user_guide/ -[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-cache]: http://pythonhosted.org/pytest-cache/ [pytest-pdb]: https://docs.pytest.org/en/6.2.x/usage.html#dropping-to-pdb-python-debugger-on-failures -[pytest-pylint]:https://github.com/carsongee/pytest-pylint -[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest-pylint]: https://github.com/carsongee/pytest-pylint +[pytest-subtests]: https://github.com/pytest-dev/pytest-subtests [pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini [python command line]: https://docs.python.org/3/using/cmdline.html [python-m-pip]: https://snarky.ca/why-you-should-use-python-m-pip/ [quick-and-dirty]: https://snarky.ca/a-quick-and-dirty-guide-on-how-to-install-packages-for-python/ -[tutorial from pycqa.org]: https://pylint.pycqa.org/en/v2.17.2/tutorial.html +[tutorial from pylint.readthedocs.io]: https://pylint.readthedocs.io/en/v2.17.7/tutorial.html [working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers From cf0f7c029f9a5227ffc56dfad56c9b14dfdecead Mon Sep 17 00:00:00 2001 From: Gautzilla <72027971+Gautzilla@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:27:51 +0200 Subject: [PATCH 694/826] remove extra space (#3749) an extra space made the markdown fail displaying _keys_ in italics --- exercises/concept/inventory-management/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/inventory-management/.docs/introduction.md b/exercises/concept/inventory-management/.docs/introduction.md index 2b9ef011e1..738f36ef75 100644 --- a/exercises/concept/inventory-management/.docs/introduction.md +++ b/exercises/concept/inventory-management/.docs/introduction.md @@ -120,7 +120,7 @@ KeyError: 'name' ## Looping through/Iterating over a Dictionary -Looping through a dictionary using `for item in dict` or `while item` will iterate over only the _keys _ by default. +Looping through a dictionary using `for item in dict` or `while item` will iterate over only the _keys_ by default. You can access the _values_ within the same loop by using _square brackets_: ```python From b189a0895cc03e12ff4188faa5eb331fb482762c Mon Sep 17 00:00:00 2001 From: Gautzilla <72027971+Gautzilla@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:42:05 +0200 Subject: [PATCH 695/826] fix typos (#3750) Remove additional apostrophe in _of that dish's' ingredients._ Remove extra _in_ in _Within in each category_. --- exercises/concept/cater-waiter/.docs/instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/cater-waiter/.docs/instructions.md b/exercises/concept/cater-waiter/.docs/instructions.md index 2d54bb0ed2..696883c3c9 100644 --- a/exercises/concept/cater-waiter/.docs/instructions.md +++ b/exercises/concept/cater-waiter/.docs/instructions.md @@ -42,7 +42,7 @@ Implement the `check_drinks(, )` function that ta The guest list includes diners with different dietary needs, and your staff will need to separate the dishes into Vegan, Vegetarian, Paleo, Keto, and Omnivore. -Implement the `categorize_dish(, )` function that takes a dish name and a `set` of that dish's' ingredients. +Implement the `categorize_dish(, )` function that takes a dish name and a `set` of that dish's ingredients. The function should return a string with the `dish name: ` (_which meal category the dish belongs to_). All dishes will "fit" into one of the categories imported from `sets_categories_data.py` (VEGAN, VEGETARIAN, PALEO, KETO, or OMNIVORE). @@ -125,7 +125,7 @@ appetizers = ['Kingfish Lettuce Cups','Avocado Deviled Eggs','Satay Steak Skewer ## 7. Find Ingredients Used in Only One Recipe -Within in each category (_Vegan, Vegetarian, Paleo, Keto, Omnivore_), you're going to pull out ingredients that appear in only one dish. +Within each category (_Vegan, Vegetarian, Paleo, Keto, Omnivore_), you're going to pull out ingredients that appear in only one dish. These "singleton" ingredients will be assigned a special shopper to ensure they're not forgotten in the rush to get everything else done. Implement the `singleton_ingredients(, )` function that takes a `list` of dishes and a `_INTERSECTIONS` constant for the same category. From e06436a8bd7bdee06ebed8ccc9aecb169f730371 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 31 Jul 2024 10:39:52 -0700 Subject: [PATCH 696/826] [Little Sisters Vocabulary] Update instructions.md (#3752) Rephrased line 7 to omit the usage of `there's`. --- exercises/concept/little-sisters-vocab/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/little-sisters-vocab/.docs/instructions.md b/exercises/concept/little-sisters-vocab/.docs/instructions.md index 2772ce18d1..2658bb980a 100644 --- a/exercises/concept/little-sisters-vocab/.docs/instructions.md +++ b/exercises/concept/little-sisters-vocab/.docs/instructions.md @@ -4,7 +4,7 @@ You are helping your younger sister with her English vocabulary homework, which Her class is learning to create new words by adding _prefixes_ and _suffixes_. Given a set of words, the teacher is looking for correctly transformed words with correct spelling by adding the prefix to the beginning or the suffix to the ending. -There's four activities in the assignment, each with a set of text or words to work with. +The assignment has four activities, each with a set of text or words to work with. ## 1. Add a prefix to a word From bcb530035306da95b1118b0cb1cf5f2c9ae625ed Mon Sep 17 00:00:00 2001 From: colinleach Date: Wed, 31 Jul 2024 18:37:03 -0700 Subject: [PATCH 697/826] [Matching Brackets] draft approaches (#3670) * [Matching Brackets] draft approaches * [Matching Brackets] Approaches Review & Edits * Additional grammar and spelling edits * Final Edits Hopefully, the final edits. :smile: * Un crossed left vs right --------- Co-authored-by: BethanyG --- .../matching-brackets/.approaches/config.json | 30 +++ .../.approaches/introduction.md | 78 ++++++++ .../repeated-substitution/content.md | 67 +++++++ .../repeated-substitution/snippet.txt | 5 + .../.approaches/stack-match/content.md | 50 +++++ .../.approaches/stack-match/snippet.txt | 8 + .../matching-brackets/.articles/config.json | 14 ++ .../.articles/performance/code/Benchmark.py | 184 ++++++++++++++++++ .../performance/code/run_times.feather | Bin 0 -> 3018 bytes .../.articles/performance/content.md | 41 ++++ .../.articles/performance/snippet.md | 3 + 11 files changed, 480 insertions(+) create mode 100644 exercises/practice/matching-brackets/.approaches/config.json create mode 100644 exercises/practice/matching-brackets/.approaches/introduction.md create mode 100644 exercises/practice/matching-brackets/.approaches/repeated-substitution/content.md create mode 100644 exercises/practice/matching-brackets/.approaches/repeated-substitution/snippet.txt create mode 100644 exercises/practice/matching-brackets/.approaches/stack-match/content.md create mode 100644 exercises/practice/matching-brackets/.approaches/stack-match/snippet.txt create mode 100644 exercises/practice/matching-brackets/.articles/config.json create mode 100644 exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/matching-brackets/.articles/performance/code/run_times.feather create mode 100644 exercises/practice/matching-brackets/.articles/performance/content.md create mode 100644 exercises/practice/matching-brackets/.articles/performance/snippet.md diff --git a/exercises/practice/matching-brackets/.approaches/config.json b/exercises/practice/matching-brackets/.approaches/config.json new file mode 100644 index 0000000000..295cbca6e3 --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/config.json @@ -0,0 +1,30 @@ +{ + "introduction": { + "authors": [ + "colinleach", + "BethanyG" + ] + }, + "approaches": [ + { + "uuid": "449c828e-ce19-4930-83ab-071eb2821388", + "slug": "stack-match", + "title": "Stack Match", + "blurb": "Maintain context during stream processing by use of a stack.", + "authors": [ + "colinleach", + "BethanyG" + ] + }, + { + "uuid": "b4c42162-751b-42c8-9368-eed9c3f4e4c8", + "slug": "repeated-substitution", + "title": "Repeated Substitution", + "blurb": "Use substring replacement to iteratively simplify the string.", + "authors": [ + "colinleach", + "BethanyG" + ] + } + ] +} diff --git a/exercises/practice/matching-brackets/.approaches/introduction.md b/exercises/practice/matching-brackets/.approaches/introduction.md new file mode 100644 index 0000000000..0096dac45c --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/introduction.md @@ -0,0 +1,78 @@ +# Introduction + +The aim in this exercise is to determine whether opening and closing brackets are properly paired within the input text. + +These brackets may be nested deeply (think Lisp code) and/or dispersed among a lot of other text (think complex LaTeX documents). + +Community solutions fall into two main groups: + +1. Those which make a single pass or loop through the input string, maintaining necessary context for matching. +2. Those which repeatedly make global substitutions within the text for context. + + +## Single-pass approaches + +```python +def is_paired(input_string): + bracket_map = {"]" : "[", "}": "{", ")":"("} + tracking = [] + + for element in input_string: + if element in bracket_map.values(): + tracking.append(element) + if element in bracket_map: + if not tracking or (tracking.pop() != bracket_map[element]): + return False + return not tracking +``` + +The key in this approach is to maintain context by pushing open brackets onto some sort of stack (_in this case appending to a `list`_), then checking if there is a corresponding closing bracket to pair with the top stack item. + +See [stack-match][stack-match] approaches for details. + + +## Repeated-substitution approaches + +```python +def is_paired(text): + text = "".join(item for item in text if item in "()[]{}") + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()","").replace("[]", "").replace("{}","") + return not text +``` + +In this approach, we first remove any non-bracket characters, then use a loop to repeatedly remove inner bracket pairs. + +See [repeated-substitution][repeated-substitution] approaches for details. + + +## Other approaches + +Languages prizing immutibility are likely to use techniques such as `foldl()` or recursive matching, as discussed on the [Scala track][scala]. + +This is possible in Python, but can read as unidiomatic and will (likely) result in inefficient code if not done carefully. + +For anyone wanting to go down the functional-style path, Python has [`functools.reduce()`][reduce] for folds and added [structural pattern matching][pattern-matching] in Python 3.10. + +Recursion is not highly optimised in Python and there is no tail call optimization, but the default stack depth of 1000 should be more than enough for solving this problem recursively. + + +## Which approach to use + +For short, well-defined input strings such as those currently in the test file, repeated-substitution allows a passing solution in very few lines of code. +But as input grows, this method could become less and less performant, due to the multiple passes and changes needed to determine matches. + +The single-pass strategy of the stack-match approach allows for stream processing, scales linearly (_`O(n)` time complexity_) with text length, and will remain performant for very large inputs. + +Examining the community solutions published for this exercise, it is clear that many programmers prefer the stack-match method which avoids the repeated string copying of the substitution approach. + +Thus it is interesting and perhaps humbling to note that repeated-substitution is **_at least_** as fast in benchmarking, even with large (>30 kB) input strings! + +See the [performance article][article-performance] for more details. + +[article-performance]:https://exercism.org/tracks/python/exercises/matching-brackets/articles/performance +[pattern-matching]: https://docs.python.org/3/whatsnew/3.10.html#pep-634-structural-pattern-matching +[reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[repeated-substitution]: https://exercism.org/tracks/python/exercises/matching-brackets/approaches/repeated-substitution +[scala]: https://exercism.org/tracks/scala/exercises/matching-brackets/dig_deeper +[stack-match]: https://exercism.org/tracks/python/exercises/matching-brackets/approaches/stack-match diff --git a/exercises/practice/matching-brackets/.approaches/repeated-substitution/content.md b/exercises/practice/matching-brackets/.approaches/repeated-substitution/content.md new file mode 100644 index 0000000000..2c8c17d637 --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/repeated-substitution/content.md @@ -0,0 +1,67 @@ +# Repeated Substitution + + +```python +def is_paired(text): + text = "".join([element for element in text if element in "()[]{}"]) + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()","").replace("[]", "").replace("{}","") + return not text +``` + +In this approach, the steps are: + +1. Remove all non-bracket characters from the input string (_as done through the filter clause in the list-comprehension above_). +2. Iteratively remove all remaining bracket pairs: this reduces nesting in the string from the inside outwards. +3. Test for a now empty string, meaning all brackets have been paired. + + +The code above spells out the approach particularly clearly, but there are (of course) several possible variants. + + +## Variation 1: Walrus Operator within a Generator Expression + + +```python +def is_paired(input_string): + symbols = "".join(char for char in input_string if char in "{}[]()") + while (pair := next((pair for pair in ("{}", "[]", "()") if pair in symbols), False)): + symbols = symbols.replace(pair, "") + return not symbols +``` + +The second solution above does essentially the same thing as the initial approach, but uses a generator expression assigned with a [walrus operator][walrus] `:=` (_introduced in Python 3.8_) in the `while-loop` test. + + +## Variation 2: Regex Substitution in a While Loop + +Regex enthusiasts can modify the previous approach, using `re.sub()` instead of `string.replace()` in the `while-loop` test: + +```python +import re + +def is_paired(text: str) -> bool: + text = re.sub(r'[^{}\[\]()]', '', text) + while text != (text := re.sub(r'{\}|\[]|\(\)', '', text)): + continue + return not bool(text) +``` + + +## Variation 3: Regex Substitution and Recursion + + +It is possible to combine `re.sub()` and recursion in the same solution, though not everyone would view this as idiomatic Python: + + +```python +import re + +def is_paired(input_string): + replaced = re.sub(r"[^\[\(\{\}\)\]]|\{\}|\(\)|\[\]", "", input_string) + return not input_string if input_string == replaced else is_paired(replaced) +``` + +Note that solutions using regular expressions ran slightly *slower* than `string.replace()` solutions in benchmarking, so adding this type of complexity brings no benefit to this problem. + +[walrus]: https://martinheinz.dev/blog/79/ diff --git a/exercises/practice/matching-brackets/.approaches/repeated-substitution/snippet.txt b/exercises/practice/matching-brackets/.approaches/repeated-substitution/snippet.txt new file mode 100644 index 0000000000..0fa6d54abd --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/repeated-substitution/snippet.txt @@ -0,0 +1,5 @@ +def is_paired(text): + text = "".join(element for element in text if element in "()[]{}") + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()","").replace("[]", "").replace("{}","") + return not text \ No newline at end of file diff --git a/exercises/practice/matching-brackets/.approaches/stack-match/content.md b/exercises/practice/matching-brackets/.approaches/stack-match/content.md new file mode 100644 index 0000000000..9619e83390 --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/stack-match/content.md @@ -0,0 +1,50 @@ +# Stack Match + + +```python +def is_paired(input_string): + bracket_map = {"]" : "[", "}": "{", ")":"("} + stack = [] + + for element in input_string: + if element in bracket_map.values(): + stack.append(element) + if element in bracket_map: + if not stack or (stack.pop() != bracket_map[element]): + return False + return not stack +``` + +The point of this approach is to maintain a context of which bracket sets are currently "open": + +- If a left bracket is found, push it onto the stack (_append it to the `list`_). +- If a right bracket is found, **and** it pairs with the last item placed on the stack, pop the bracket off the stack and continue. +- If there is a mismatch, for example `'['` with `'}'` or there is no left bracket on the stack, the code can immediately terminate and return `False`. +- When all the input text is processed, determine if the stack is empty, meaning all left brackets were matched. + +In Python, a [`list`][concept:python/lists]() is a good implementation of a stack: it has [`list.append()`][list-append] (_equivalent to a "push"_) and [`lsit.pop()`][list-pop] methods built in. + +Some solutions use [`collections.deque()`][collections-deque] as an alternative implementation, though this has no clear advantage (_since the code only uses appends to the right-hand side_) and near-identical runtime performance. + +The default iteration for a dictionary is over the _keys_, so the code above uses a plain `bracket_map` to search for right brackets, while `bracket_map.values()` is used to search for left brackets. + +Other solutions created two sets of left and right brackets explicitly, or searched a string representation: + +```python + if element in ']})': +``` + +Such changes made little difference to code length or readability, but ran about 5-fold faster than the dictionary-based solution. + +At the end, success is an empty stack, tested above by using the [False-y quality][falsey] of `[]` (_as Python programmers often do_). + +To be more explicit, we could alternatively use an equality: + +```python + return stack == [] +``` + +[list-append]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists +[list-pop]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists +[collections-deque]: https://docs.python.org/3/library/collections.html#collections.deque +[falsey]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/practice/matching-brackets/.approaches/stack-match/snippet.txt b/exercises/practice/matching-brackets/.approaches/stack-match/snippet.txt new file mode 100644 index 0000000000..571b6792a6 --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/stack-match/snippet.txt @@ -0,0 +1,8 @@ + bracket_map = {"]" : "[", "}": "{", ")":"("} + stack = [] + for element in input_string: + if element in bracket_map.values(): tracking.append(element) + if element in bracket_map: + if not stack or (stack.pop() != bracket_map[element]): + return False + return not stack \ No newline at end of file diff --git a/exercises/practice/matching-brackets/.articles/config.json b/exercises/practice/matching-brackets/.articles/config.json new file mode 100644 index 0000000000..0a5a8856a3 --- /dev/null +++ b/exercises/practice/matching-brackets/.articles/config.json @@ -0,0 +1,14 @@ +{ + "articles": [ + { + "uuid": "af7a43b5-c135-4809-9fb8-d84cdd5138d5", + "slug": "performance", + "title": "Performance", + "blurb": "Compare a variety of solutions using benchmarking data.", + "authors": [ + "colinleach", + "BethanyG" + ] + } + ] +} diff --git a/exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py b/exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..1ca6ff0025 --- /dev/null +++ b/exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py @@ -0,0 +1,184 @@ +import timeit + +import pandas as pd +import numpy as np +import requests + + +# ------------ FUNCTIONS TO TIME ------------- # + +def stack_match1(input_string): + bracket_map = {"]" : "[", "}": "{", ")":"("} + tracking = [] + + for element in input_string: + if element in bracket_map.values(): + tracking.append(element) + if element in bracket_map: + if not tracking or (tracking.pop() != bracket_map[element]): + return False + return not tracking + + +def stack_match2(input_string): + opening = {'[', '{', '('} + closing = {']', '}', ')'} + pairs = {('[', ']'), ('{', '}'), ('(', ')')} + stack = list() + + for char in input_string: + if char in opening: + stack.append(char) + elif char in closing: + if not stack or (stack.pop(), char) not in pairs: + return False + return stack == [] + + + +def stack_match3(input_string): + BRACKETS = {'(': ')', '[': ']', '{': '}'} + END_BRACKETS = {')', ']', '}'} + + stack = [] + + def is_valid(char): + return stack and stack.pop() == char + + for char in input_string: + if char in BRACKETS: + stack.append(BRACKETS[char]) + elif char in END_BRACKETS and not is_valid(char): + return False + + return not stack + + +def stack_match4(input_string): + stack = [] + r = {')': '(', ']': '[', '}': '{'} + for c in input_string: + if c in '[{(': + stack.append(c) + if c in ']})': + if not stack: + return False + if stack[-1] == r[c]: + stack.pop() + else: + return False + return not stack + + +from collections import deque +from typing import Deque + + +def stack_match5(text: str) -> bool: + """ + Determine if the given text properly closes any opened brackets. + """ + PUSH = {"[": "]", "{": "}", "(": ")"} + PULL = set(PUSH.values()) + + stack: Deque[str] = deque() + for char in text: + if char in PUSH: + stack.append(PUSH[char]) + elif char in PULL: + if not stack or char != stack.pop(): + return False + return not stack + + +def repeated_substitution1(text): + text = "".join(x for x in text if x in "()[]{}") + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()","").replace("[]", "").replace("{}","") + return not text + + +def repeated_substitution2(input_string): + symbols = "".join(c for c in input_string if c in "{}[]()") + while (pair := next((pair for pair in ("{}", "[]", "()") if pair in symbols), False)): + symbols = symbols.replace(pair, "") + return not symbols + + +import re + +def repeated_substitution3(str_: str) -> bool: + str_ = re.sub(r'[^{}\[\]()]', '', str_) + while str_ != (str_ := re.sub(r'{\}|\[]|\(\)', '', str_)): + pass + return not bool(str_) + + +def repeated_substitution4(input_string): + replaced = re.sub(r"[^\[\(\{\}\)\]]|\{\}|\(\)|\[\]", "", input_string) + return not input_string if input_string == replaced else repeated_substitution4(replaced) + +## ---------END FUNCTIONS TO BE TIMED-------------------- ## + +## -------- Timing Code Starts Here ---------------------## + +def get_file(url): + resp = requests.get(url) + return resp.text + +short = "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)" +mars_moons = get_file("https://raw.githubusercontent.com/colinleach/PTYS516/main/term_paper/term_paper.tex") +galaxy_cnn = get_file("https://raw.githubusercontent.com/colinleach/proj502/main/project_report/report.tex") + + +# Input Data Setup +inputs = [short, mars_moons, galaxy_cnn] + +# Ensure the code doesn't terminate early with a mismatch +assert all([stack_match1(txt) for txt in inputs]) + +# #Set up columns and rows for Pandas Data Frame +col_headers = ['short', 'mars_moons', 'galaxy_cnn'] +row_headers = [ + "stack_match1", + "stack_match2", + "stack_match3", + "stack_match4", + "stack_match5", + + "repeated_substitution1", + "repeated_substitution2", + "repeated_substitution3", + "repeated_substitution4" + ] + +# Empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# Function List to Call When Timing +functions = [stack_match1, stack_match2, stack_match3, stack_match4, stack_match5, + repeated_substitution1, repeated_substitution2, repeated_substitution3, repeated_substitution4] + +# Run timings using timeit.autorange(). Run Each Set 3 Times. +for function, title in zip(functions, row_headers): + timings = [[ + timeit.Timer(lambda: function(data), globals=globals()).autorange()[1] / + timeit.Timer(lambda: function(data), globals=globals()).autorange()[0] + for data in inputs] for rounds in range(3)] + + # Only the fastest Cycle counts. + timing_result = min(timings) + + print(f'{title}', f'Timings : {timing_result}') + # Insert results into the dataframe + df.loc[title, col_headers[0]:col_headers[-1]] = timing_result + +# Save the data to avoid constantly regenerating it +df.to_feather('run_times.feather') +print("\nDataframe saved to './run_times.feather'") + +# The next bit is useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".2e")) + diff --git a/exercises/practice/matching-brackets/.articles/performance/code/run_times.feather b/exercises/practice/matching-brackets/.articles/performance/code/run_times.feather new file mode 100644 index 0000000000000000000000000000000000000000..72ef3124185e4bfb6b7e03ba43c83a667d6b1a6d GIT binary patch literal 3018 zcmeGeTWAzl^t#!^RhAIJrlBaVL%@g{vzu)iMCWdZ#we&Eq^Z!z5K9m;?I5IAh^is^s1($cWK2y6+dQ8Ol4Q~pxkpx{8c7lkZT1MJ zgvpY*V6ULEm<&tQq@sXCI4Ub4DW7KAl8jUw1%;#<4Qe4-0BJQDi>DSCT35FmG>{&X z4HjYs1Myx#RzW%qwwl!_37t-mhGx=T!iHN3i6?u+i$u?iYipGpO|6*Tq7&*@1 zCexe2Cu@U3jOhs}rfG0$_RZUP@7VBRrm&+aDX6N84d?UTt>Hq~Tp(EBFl=_;?$-H1 z^GnucW@tJO1SQ&~Gd+cI5Kl2(*N))cdt^Nk)l{TV?BrItM*p=g*3!I z=Ervc20#D+@d)G^WKA*VT9rkEA6W&kC>J>2$=6*q*A?NqHJlC~7?`8kdm!WzmvfNE zuaVCN6Cn>Q`7*K^a<_ym=h<9l<@~Zm_KLwCkA09udCYM_Ah}_Hs`+{-2&*}Ub)f?I zSV`PmD=Bhv-zTtWEXb?@TUa?v89XoCAL>|P$>0x90naa&mc!;#$s%;x7#TdnpRn;W zc!uxE@ef5aIJMSwTKq#6j=w^_*?oNz-fZb}3i~QL14DogjL=u-?tS}I+&_GO=asQ> zF%+2lcgAsBd{umlZGHH?IFL4;e{g3z?Q(qkv~&~GmP z_{WCnI{MYik*U9qe?sS~I({8}(M~sZoSj|Ue$YIb0{Xw)acc0t-ErFAJ??h=u#2{w z9`@DTYoc9c2Ukq~9iS&0ItPcQn&{~4ld&R~pI+bn=iOUZ{WKg7&(-(0(T=lsABm;y zrv9q@^8s`KDcF8004f1$0Ja0P0z?4%04@OVk(pTM+XTdi34;Z{kYdaTM!atFp%0)I z!0#sWu8(y&4pl)8Nr_}v!iXA40}6n*oH*9`;Ip>2oU|atZ|TSQ$0_Eh8zLAp#0?la zh!KL0;T|wnFoarg0wZ9>(}XSVFuRv}Zng<7^<0mTt@YTGY`rh%F_Hh`$ziF8P literal 0 HcmV?d00001 diff --git a/exercises/practice/matching-brackets/.articles/performance/content.md b/exercises/practice/matching-brackets/.articles/performance/content.md new file mode 100644 index 0000000000..0d34786e73 --- /dev/null +++ b/exercises/practice/matching-brackets/.articles/performance/content.md @@ -0,0 +1,41 @@ +# Performance + +All functions were tested on three inputs, a short string from the exercise tests plus two scientific papers in $\LaTeX$ format. + +Python reported these string lengths: + +``` + short: 84 + mars_moons: 34836 + galaxy_cnn: 31468 +``` + +A total of 9 community solutions were tested: 5 variants of stack-match and 4 of repeated-substitution. +Full details are in the [benchmark code][benchmark-code], including URLs for the downloaded papers. +Results are summarized in the table below, with all times in seconds: + + +| | short | mars_moons | galaxy_cnn | +|:-----------------------|:--------:|:------------:|:------------:| +| stack_match4 | 1.77e-06 | 5.92e-04 | 5.18e-04 | +| stack_match2 | 1.71e-06 | 7.38e-04 | 6.64e-04 | +| stack_match3 | 1.79e-06 | 7.72e-04 | 6.95e-04 | +| stack_match5 | 1.70e-06 | 7.79e-04 | 6.97e-04 | +| stack_match1 | 5.64e-06 | 21.9e-04 | 39.7e-04 | +| repeated_substitution1 | 1.20e-06 | 3.50e-04 | 3.06e-04 | +| repeated_substitution2 | 1.86e-06 | 3.58e-04 | 3.15e-04 | +| repeated_substitution3 | 4.27e-06 | 14.0e-04 | 12.5e-04 | +| repeated_substitution4 | 4.96e-06 | 14.9e-04 | 13.5e-04 | + + +Overall, most of these solutions had fairly similar performance, and runtime scaled similarly with input length. + +There is certainly no evidence for either class of solutions being systematically better than the other. + +The slowest was `stack_match1`, which did a lot of lookups in dictionary. +keys and values. Searching instead in sets or strings gave a small but perhaps useful improvement. + +Among the repeated-substitution solutions, the first two used standard Python string operations, running slightly faster than the second two which use regular expressions. + + +[benchmark-code]: https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py diff --git a/exercises/practice/matching-brackets/.articles/performance/snippet.md b/exercises/practice/matching-brackets/.articles/performance/snippet.md new file mode 100644 index 0000000000..1479ad508e --- /dev/null +++ b/exercises/practice/matching-brackets/.articles/performance/snippet.md @@ -0,0 +1,3 @@ +# Performance + +Compare a variety of solutions using benchmarking data. From 56d158766f61534658d65fa068d9076ccc80146a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 13 Aug 2024 12:01:08 -0700 Subject: [PATCH 698/826] Attempt at fix for issue described in http://forum.exercism.org/t/github-ci-docker-compose-not-found-error-for-python/12487. (#3754) --- .github/workflows/test-runner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index d57a2d290c..b9600cc8b9 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -12,4 +12,4 @@ jobs: steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Run test-runner - run: docker-compose run test-runner + run: docker compose run test-runner From adaa483944c0eb133deab5c1fbb828e15ec8e413 Mon Sep 17 00:00:00 2001 From: Ela Bogucka <9089666+ebogucka@users.noreply.github.com> Date: Tue, 13 Aug 2024 21:09:50 +0200 Subject: [PATCH 699/826] [Collatz Conjecture] Approaches - missing words fix (#3753) * Added missing words in General guidance text * Update exercises/practice/collatz-conjecture/.approaches/introduction.md --------- Co-authored-by: BethanyG --- .../practice/collatz-conjecture/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/collatz-conjecture/.approaches/introduction.md b/exercises/practice/collatz-conjecture/.approaches/introduction.md index 6a6d487f26..58d7a65f38 100644 --- a/exercises/practice/collatz-conjecture/.approaches/introduction.md +++ b/exercises/practice/collatz-conjecture/.approaches/introduction.md @@ -6,7 +6,7 @@ You can also solve it by using if and else statements or the ternary operator. ## General guidance -The key to this exercise is to check if the number and then do the correct operation. +The key to this exercise is to check if the number is even or odd and then perform the correct operation. Under this process you are supposed to count how many steps it takes to get to one. ## Approach: If/Else From 00d5bcc9de36dcd2300b6b05d08cdcf63d660d9b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 23 Aug 2024 13:48:02 -0700 Subject: [PATCH 700/826] Fixed instructions and tests per https://forum.exercism.org/t/testing-data-in-mecha-munch-management-dict-methods-py-task3/12638. (#3756) [no important files changed] --- .../concept/mecha-munch-management/.docs/instructions.md | 4 ++-- exercises/concept/mecha-munch-management/dict_methods_test.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index c7cf733276..13d88f39fa 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -59,10 +59,10 @@ The function should return the new/updated "ideas" dictionary. ```python >>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, -(('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)) +(('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)) ... -{'Banana Bread' : {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, +{'Banana Bread' : {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}} >>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 622f425d5a..63fc1874a0 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -54,7 +54,7 @@ def test_update_recipes(self): input_data = [ ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, - (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)), + (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)), ({'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, 'Blueberry Pie': {'Blueberries': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, @@ -70,7 +70,7 @@ def test_update_recipes(self): ] output_data = [ - {'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, + {'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, 'Raspberry Pie': {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, {'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, 'Blueberry Pie': {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}}, From 951d4d35c1d65a77794ba8670a965ece31577f87 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Aug 2024 09:45:40 -0700 Subject: [PATCH 701/826] [Meltdown Mitigation]: Correct Typo in introduction.md (#3758) great --> greater on line 53. --- exercises/concept/meltdown-mitigation/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/meltdown-mitigation/.docs/introduction.md b/exercises/concept/meltdown-mitigation/.docs/introduction.md index 0eb3d88ccd..39d402bc44 100644 --- a/exercises/concept/meltdown-mitigation/.docs/introduction.md +++ b/exercises/concept/meltdown-mitigation/.docs/introduction.md @@ -50,7 +50,7 @@ elif y > z: else: print("z is greater than x and y") ... ->>> z is great than x and y +>>> z is greater than x and y ``` [Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: From a74dfbd3309bdbe8fc981e7e7bc9678c6828669a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Aug 2024 13:10:05 -0700 Subject: [PATCH 702/826] Msc spelling and grammar fixes for concepts and concept exercises. (#3759) --- concepts/basics/introduction.md | 2 +- concepts/classes/about.md | 4 ++-- concepts/conditionals/about.md | 4 ++-- exercises/concept/black-jack/.docs/instructions.md | 8 ++++---- exercises/concept/ellens-alien-game/.docs/introduction.md | 2 +- .../concept/guidos-gorgeous-lasagna/.docs/introduction.md | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index dc062bc70d..2a874394eb 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -89,7 +89,7 @@ def add_two_numbers(number_one, number_two): # the variable will also return the value. >>> sum_with_return = add_two_numbers(5, 6) >>> print(sum_with_return) -7 +11 ``` Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. diff --git a/concepts/classes/about.md b/concepts/classes/about.md index f50af7321d..f88ce892f3 100644 --- a/concepts/classes/about.md +++ b/concepts/classes/about.md @@ -314,12 +314,12 @@ class MyClass: # This will compile and run without error, but has no current functionality. def pending_functionality(self): - # Stubbing or placholding the body of this method. + # Stubbing or place-holding the body of this method. pass ``` [class method]: https://stackoverflow.com/questions/17134653/difference-between-class-and-instance-methods -[dunder]: https://www.dataindependent.com/python/python-glossary/python-dunder/ +[dunder]: https://mathspp.com/blog/pydonts/dunder-methods [oop]: https://www.educative.io/blog/object-oriented-programming [dot notation]: https://stackoverflow.com/questions/45179186/understanding-the-dot-notation-in-python [shadowing]: https://oznetnerd.com/2017/07/17/python-shadowing/ diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index 8f0ae8fde0..5a518d3dd6 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -92,13 +92,13 @@ Conditionals can also be nested. >>> driving_status(63, 78) -'Unlicsensed!' +'Unlicensed!' >>> driving_status(16, 81) 'Student driver, needs supervision.' >>> driving_status(23, 80) -'Fully licsensed driver.' +'Fully licensed driver.' ``` ## Conditional expressions or "ternary operators" diff --git a/exercises/concept/black-jack/.docs/instructions.md b/exercises/concept/black-jack/.docs/instructions.md index 21fc971e23..e95c5fadb9 100644 --- a/exercises/concept/black-jack/.docs/instructions.md +++ b/exercises/concept/black-jack/.docs/instructions.md @@ -79,15 +79,15 @@ Remember: the value of the hand with the ace needs to be as high as possible _wi ## 4. Determine a "Natural" or "Blackjack" Hand -If the first two cards a player is dealt are an ace (A) and a ten-card (_10, K , Q or J_), then the player has a score of 21. -This is known as a blackjack hand. +If a player is dealt an ace (`A`) and a ten-card (10, `K`, `Q`, or `J`) as their first two cards, then the player has a score of 21. +This is known as a **blackjack** hand. Define the `is_blackjack(, )` function with parameters `card_one` and `card_two`, which are a pair of cards. -Determine if the two-card hand is a `blackjack`, and return the boolean `True` if it is, `False` otherwise. +Determine if the two-card hand is a **blackjack**, and return the boolean `True` if it is, `False` otherwise. **Note** : The score _calculation_ can be done in many ways. -But if possible, we'd like you to check if there is an ace and a ten-card **_in_** the hand (or at a certain position), as opposed to _summing_ the hand values. +But if possible, we'd like you to check if there is an ace and a ten-card **_in_** the hand (_or at a certain position_), as opposed to _summing_ the hand values. ```python >>> is_blackjack('A', 'K') diff --git a/exercises/concept/ellens-alien-game/.docs/introduction.md b/exercises/concept/ellens-alien-game/.docs/introduction.md index ac99054de1..ea1fc940fa 100644 --- a/exercises/concept/ellens-alien-game/.docs/introduction.md +++ b/exercises/concept/ellens-alien-game/.docs/introduction.md @@ -261,7 +261,7 @@ class MyClass: [calling]: https://www.pythonmorsels.com/topics/calling-a-function [class method]: https://stackoverflow.com/questions/17134653/difference-between-class-and-instance-methods -[dunder]: https://www.dataindependent.com/python/python-glossary/python-dunder/ +[dunder]: https://mathspp.com/blog/pydonts/dunder-methods [imperative]: https://en.wikipedia.org/wiki/Imperative_programming [declarative]: https://en.wikipedia.org/wiki/Declarative_programming [oop]: https://www.digitalocean.com/community/tutorials/how-to-construct-classes-and-define-objects-in-python-3 diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index c3fe37541e..ffe2bedd6a 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -100,8 +100,8 @@ def add_two_numbers(number_one, number_two): >>> add_two_numbers(3, 4) 7 -# Assigning the function call to a variable and printing -# the variable will also return the value. +# Assigning the function call to a variable and printing it +# will also return the value. >>> sum_with_return = add_two_numbers(5, 6) >>> print(sum_with_return) 11 @@ -153,7 +153,7 @@ Dot (`.`) notation is used for calling functions defined inside a class or modul 27 -# A mis-match between the number of parameters and the number of arguments will raise an error. +# A mismatch between the number of parameters and the number of arguments will raise an error. >>> number_to_the_power_of(4,) ... Traceback (most recent call last): From 7ea631f4402bba683fabeba649a248ef946d76fd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 26 Aug 2024 14:34:27 -0700 Subject: [PATCH 703/826] [Transpose & generate_tests.py]: Added `join` env Function & Changed Template (#3760) [no important files changed] * Added join input function and changed template and test file for transpose exercise. * Regenerated file to replace lines with text. --- bin/generate_tests.py | 3 + .../practice/transpose/.meta/template.j2 | 15 +- exercises/practice/transpose/transpose.py | 2 +- .../practice/transpose/transpose_test.py | 138 +++++++----------- 4 files changed, 64 insertions(+), 94 deletions(-) diff --git a/bin/generate_tests.py b/bin/generate_tests.py index a3089eb8b4..2ad23a9b5f 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -204,6 +204,8 @@ def regex_find(s: str, find: str) -> List[Any]: def regex_split(s: str, find: str) -> List[str]: return re.split(find, s) +def join_test_inputs(test_inputs: list) -> str: + return "\n".join(test_inputs) def filter_test_cases(cases: List[TypeJSON], opts: TestsTOML) -> List[TypeJSON]: """ @@ -409,6 +411,7 @@ def generate( env.filters["regex_replace"] = regex_replace env.filters["regex_find"] = regex_find env.filters["regex_split"] = regex_split + env.filters["join_test_inputs"] = join_test_inputs env.filters["zip"] = zip env.filters["parse_datetime"] = parse_datetime env.filters["escape_invalid_escapes"] = escape_invalid_escapes diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2 index e622947b10..debe8fd766 100644 --- a/exercises/practice/transpose/.meta/template.j2 +++ b/exercises/practice/transpose/.meta/template.j2 @@ -5,9 +5,16 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} - def test_{{ case["description"] | to_snake }}(self): - lines = {{ case["input"]["lines"] }} - expected = {{ case["expected"] }} - self.assertEqual({{ case["property"] }}("\n".join(lines)), "\n".join(expected)) + def test_{{ case["description"] | to_snake }}(self): + {%- if case["input"]["lines"] | length > 0 %} + text = "{{+ case["input"]["lines"] | join_test_inputs | replace('\n','\\n') }}" + expected = "{{+ case["expected"] | join_test_inputs | replace('\n','\\n') }}" + + {%- else %} + text = "" + expected = "" + {%- endif %} + + self.assertEqual({{ case["property"] }}(text), expected) {% endfor %} diff --git a/exercises/practice/transpose/transpose.py b/exercises/practice/transpose/transpose.py index d6814f837f..24b60afe18 100644 --- a/exercises/practice/transpose/transpose.py +++ b/exercises/practice/transpose/transpose.py @@ -1,2 +1,2 @@ -def transpose(lines): +def transpose(text): pass diff --git a/exercises/practice/transpose/transpose_test.py b/exercises/practice/transpose/transpose_test.py index d3ab85ff9f..220c8be4d0 100644 --- a/exercises/practice/transpose/transpose_test.py +++ b/exercises/practice/transpose/transpose_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-08-26 import unittest @@ -11,113 +11,73 @@ class TransposeTest(unittest.TestCase): def test_empty_string(self): - lines = [] - expected = [] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "" + expected = "" + + self.assertEqual(transpose(text), expected) def test_two_characters_in_a_row(self): - lines = ["A1"] - expected = ["A", "1"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "A1" + expected = "A\n1" + + self.assertEqual(transpose(text), expected) def test_two_characters_in_a_column(self): - lines = ["A", "1"] - expected = ["A1"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "A\n1" + expected = "A1" + + self.assertEqual(transpose(text), expected) def test_simple(self): - lines = ["ABC", "123"] - expected = ["A1", "B2", "C3"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "ABC\n123" + expected = "A1\nB2\nC3" + + self.assertEqual(transpose(text), expected) def test_single_line(self): - lines = ["Single line."] - expected = ["S", "i", "n", "g", "l", "e", " ", "l", "i", "n", "e", "."] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "Single line." + expected = "S\ni\nn\ng\nl\ne\n \nl\ni\nn\ne\n." + + self.assertEqual(transpose(text), expected) def test_first_line_longer_than_second_line(self): - lines = ["The fourth line.", "The fifth line."] - expected = [ - "TT", - "hh", - "ee", - " ", - "ff", - "oi", - "uf", - "rt", - "th", - "h ", - " l", - "li", - "in", - "ne", - "e.", - ".", - ] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "The fourth line.\nThe fifth line." + expected = "TT\nhh\nee\n \nff\noi\nuf\nrt\nth\nh \n l\nli\nin\nne\ne.\n." + + self.assertEqual(transpose(text), expected) def test_second_line_longer_than_first_line(self): - lines = ["The first line.", "The second line."] - expected = [ - "TT", - "hh", - "ee", - " ", - "fs", - "ie", - "rc", - "so", - "tn", - " d", - "l ", - "il", - "ni", - "en", - ".e", - " .", - ] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "The first line.\nThe second line." + expected = "TT\nhh\nee\n \nfs\nie\nrc\nso\ntn\n d\nl \nil\nni\nen\n.e\n ." + + self.assertEqual(transpose(text), expected) def test_mixed_line_length(self): - lines = ["The longest line.", "A long line.", "A longer line.", "A line."] - expected = [ - "TAAA", - "h ", - "elll", - " ooi", - "lnnn", - "ogge", - "n e.", - "glr", - "ei ", - "snl", - "tei", - " .n", - "l e", - "i .", - "n", - "e", - ".", - ] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "The longest line.\nA long line.\nA longer line.\nA line." + expected = "TAAA\nh \nelll\n ooi\nlnnn\nogge\nn e.\nglr\nei \nsnl\ntei\n .n\nl e\ni .\nn\ne\n." + + self.assertEqual(transpose(text), expected) def test_square(self): - lines = ["HEART", "EMBER", "ABUSE", "RESIN", "TREND"] - expected = ["HEART", "EMBER", "ABUSE", "RESIN", "TREND"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "HEART\nEMBER\nABUSE\nRESIN\nTREND" + expected = "HEART\nEMBER\nABUSE\nRESIN\nTREND" + + self.assertEqual(transpose(text), expected) def test_rectangle(self): - lines = ["FRACTURE", "OUTLINED", "BLOOMING", "SEPTETTE"] - expected = ["FOBS", "RULE", "ATOP", "CLOT", "TIME", "UNIT", "RENT", "EDGE"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "FRACTURE\nOUTLINED\nBLOOMING\nSEPTETTE" + expected = "FOBS\nRULE\nATOP\nCLOT\nTIME\nUNIT\nRENT\nEDGE" + + self.assertEqual(transpose(text), expected) def test_triangle(self): - lines = ["T", "EE", "AAA", "SSSS", "EEEEE", "RRRRRR"] - expected = ["TEASER", " EASER", " ASER", " SER", " ER", " R"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "T\nEE\nAAA\nSSSS\nEEEEE\nRRRRRR" + expected = "TEASER\n EASER\n ASER\n SER\n ER\n R" + + self.assertEqual(transpose(text), expected) def test_jagged_triangle(self): - lines = ["11", "2", "3333", "444", "555555", "66666"] - expected = ["123456", "1 3456", " 3456", " 3 56", " 56", " 5"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "11\n2\n3333\n444\n555555\n66666" + expected = "123456\n1 3456\n 3456\n 3 56\n 56\n 5" + + self.assertEqual(transpose(text), expected) From 57f11c2243818269a000b7da2e218cb4f17c8d01 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 26 Aug 2024 14:59:10 -0700 Subject: [PATCH 704/826] [POV]: Update instructions.append.md (#3761) Massaged the phrasing a bit to point students to both raising the specific error type, and including a specific error message. --- exercises/practice/pov/.docs/instructions.append.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/pov/.docs/instructions.append.md b/exercises/practice/pov/.docs/instructions.append.md index 024842736a..7b16ebbe89 100644 --- a/exercises/practice/pov/.docs/instructions.append.md +++ b/exercises/practice/pov/.docs/instructions.append.md @@ -4,6 +4,7 @@ Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. -This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" multiple `ValueErrors` if the `Tree()` class is passed a tree that cannot be reoriented, or a path cannot be found between a `start node` and an `end node`. The tests will only pass if you both `raise` the `exception` and include a message with it. +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" multiple `ValueErrors` if the `Tree()` class is passed a tree that cannot be reoriented, or a path cannot be found between a `start node` and an `end node`. +The tests will only pass if you both `raise` the expected `exception` type and include the expected message with it. Please check the tests and their expected results carefully. From e4bb420ff57c23fd41e9d1ed6f08f6d340ab845b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 29 Aug 2024 13:41:05 -0700 Subject: [PATCH 705/826] Adjusted if-elif-else examples and hints. (#3764) --- concepts/conditionals/about.md | 51 ++++++++++++++----- concepts/conditionals/introduction.md | 13 +++-- .../meltdown-mitigation/.docs/hints.md | 6 ++- .../meltdown-mitigation/.docs/introduction.md | 11 ++-- .../meltdown-mitigation/.meta/exemplar.py | 1 - 5 files changed, 55 insertions(+), 27 deletions(-) diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index 5a518d3dd6..e5a74fb527 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -8,7 +8,6 @@ Python 3.10 introduces a variant case-switch statement called `pattern matching` Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` directly, or by evaluating ["truthy" or "falsy"][truth value testing]. - ```python x = 10 y = 5 @@ -61,13 +60,15 @@ else: >>> def classic_fizzbuzz(number): if number % 3 == 0 and number % 5 == 0: - return 'FizzBuzz!' + say = 'FizzBuzz!' elif number % 5 == 0: - return 'Buzz!' + say = 'Buzz!' elif number % 3 == 0: - return 'Fizz!' + say = 'Fizz!' else: - return str(number) + say = str(number) + + return say >>> classic_fizzbuzz(15) 'FizzBuzz!' @@ -76,19 +77,44 @@ else: '13' ``` +As an alternative, the example above can be re-written to only use `if` statements with `returns`. +However, re-writing in this way might obscure that the conditions are intended to be [_mutually exclusive_][mutually-exclusive] and could lead to future bugs or maintenance issues. + + +```python +>>> def classic_fizzbuzz(number): + if number % 3 == 0 and number % 5 == 0: + return 'FizzBuzz!' + if number % 5 == 0: + return 'Buzz!' + if number % 3 == 0: + return 'Fizz!' + + return str(number) + +>>> classic_fizzbuzz(15) +'FizzBuzz!' + +>>> classic_fizzbuzz(13) +'13' +``` + + Conditionals can also be nested. ```python >>> def driving_status(driver_age, test_score): if test_score >= 80: if 18 > driver_age >= 16: - return "Student driver, needs supervision." + status = "Student driver, needs supervision." elif driver_age == 18: - return "Permitted driver, on probation." + satus = "Permitted driver, on probation." elif driver_age > 18: - return "Fully licensed driver." + status = "Fully licensed driver." else: - return "Unlicensed!" + status = "Unlicensed!" + + return status >>> driving_status(63, 78) @@ -151,8 +177,9 @@ This is Truthy. Nope. It's Falsey. ``` -[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement -[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools -[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing [boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not [comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons +[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools +[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement +[mutually-exclusive]: https://stackoverflow.com/a/22783232 +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/concepts/conditionals/introduction.md b/concepts/conditionals/introduction.md index 77bf9608ed..ee1d433620 100644 --- a/concepts/conditionals/introduction.md +++ b/concepts/conditionals/introduction.md @@ -45,10 +45,8 @@ z = 20 # The elif statement allows for the checking of more conditions. if x > y > z: - print("x is greater than y and z") elif y > x > z: - print("y is greater than x and z") else: print("z is greater than x and y") @@ -59,16 +57,17 @@ else: [Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: ```python - >>> def classic_fizzbuzz(number): if number % 3 == 0 and number % 5 == 0: - return 'FizzBuzz!' + say = 'FizzBuzz!' elif number % 5 == 0: - return 'Buzz!' + say = 'Buzz!' elif number % 3 == 0: - return 'Fizz!' + say = 'Fizz!' else: - return str(number) + say = str(number) + + return say >>> classic_fizzbuzz(15) 'FizzBuzz!' diff --git a/exercises/concept/meltdown-mitigation/.docs/hints.md b/exercises/concept/meltdown-mitigation/.docs/hints.md index e500bb4dcd..e0d1fe0ffb 100644 --- a/exercises/concept/meltdown-mitigation/.docs/hints.md +++ b/exercises/concept/meltdown-mitigation/.docs/hints.md @@ -30,13 +30,15 @@ - Comparison operators can be combined and used with conditionals. - Any number of `elif` statements can be used as decision "branches". -- Each "branch" can have a separate `return` +- Each "branch" can have a separate `return`, although it might be considered "bad form" by linting tools. +- If the linter complains, consider assigning the output of a branch to a common variable, and then `return`ing that variable. ## 3. Fail Safe Mechanism - Comparison operators can be combined and used with conditionals. - Any number of `elif` statements can be used as decision "branches". -- Each "branch" can have a separate `return` +- Each "branch" can have a separate `return`, although it might be considered "bad form" by linting tools. +- If the linter complains, consider assigning the output of a branch to a common variable, and then `return`ing that variable. [boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not diff --git a/exercises/concept/meltdown-mitigation/.docs/introduction.md b/exercises/concept/meltdown-mitigation/.docs/introduction.md index 39d402bc44..c084236abc 100644 --- a/exercises/concept/meltdown-mitigation/.docs/introduction.md +++ b/exercises/concept/meltdown-mitigation/.docs/introduction.md @@ -56,16 +56,17 @@ else: [Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: ```python - >>> def classic_fizzbuzz(number): if number % 3 == 0 and number % 5 == 0: - return 'FizzBuzz!' + say = 'FizzBuzz!' elif number % 5 == 0: - return 'Buzz!' + say = 'Buzz!' elif number % 3 == 0: - return 'Fizz!' + say = 'Fizz!' else: - return str(number) + say = str(number) + + return say >>> classic_fizzbuzz(15) 'FizzBuzz!' diff --git a/exercises/concept/meltdown-mitigation/.meta/exemplar.py b/exercises/concept/meltdown-mitigation/.meta/exemplar.py index 54cb7d5ce7..c2d8ddd7ed 100644 --- a/exercises/concept/meltdown-mitigation/.meta/exemplar.py +++ b/exercises/concept/meltdown-mitigation/.meta/exemplar.py @@ -45,7 +45,6 @@ def reactor_efficiency(voltage, current, theoretical_max_power): generated_power = voltage * current percentage_range = (generated_power / theoretical_max_power) * 100 - efficiency_level = 'unknown' if 80 <= percentage_range <= 100: efficiency_level = 'green' From ee643b03e31a3e2e064990c7eefc926a85d4cad0 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 29 Aug 2024 14:45:18 -0700 Subject: [PATCH 706/826] [Conditionals Concept] Update about.md (#3765) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TYPO! 😡 --- concepts/conditionals/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index e5a74fb527..d0b91f26ce 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -108,7 +108,7 @@ Conditionals can also be nested. if 18 > driver_age >= 16: status = "Student driver, needs supervision." elif driver_age == 18: - satus = "Permitted driver, on probation." + status = "Permitted driver, on probation." elif driver_age > 18: status = "Fully licensed driver." else: From 6b8660a7a3b266eaeb65d54686127942e4a53ce8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 05:49:20 -0700 Subject: [PATCH 707/826] Bump actions/setup-python from 5.1.1 to 5.2.0 (#3766) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.1.1 to 5.2.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/39cd14951b08e74b54015e9e001cdefcf80e669f...f677139bbe7f9c59b41e40162b753c062f5d49a3) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 72be71f702..a2257ad173 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Set up Python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: ${{ matrix.python-version }} From 63fffc04e92999faddb4eba0168f91968ac940cc Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Wed, 4 Sep 2024 15:03:30 +0100 Subject: [PATCH 708/826] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3770)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/fc1613760f6670850e29a593bbb5c9669edc23bd --- .../ping-cross-track-maintainers-team.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/ping-cross-track-maintainers-team.yml diff --git a/.github/workflows/ping-cross-track-maintainers-team.yml b/.github/workflows/ping-cross-track-maintainers-team.yml new file mode 100644 index 0000000000..b6ec9c5662 --- /dev/null +++ b/.github/workflows/ping-cross-track-maintainers-team.yml @@ -0,0 +1,16 @@ +name: Ping cross-track maintainers team + +on: + pull_request_target: + types: + - opened + +permissions: + pull-requests: write + +jobs: + ping: + if: github.repository_owner == 'exercism' # Stops this job from running on forks + uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main + secrets: + github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} From b061dd047fcd0cf851d90cf6888868323aea4db4 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 4 Sep 2024 16:05:04 +0200 Subject: [PATCH 709/826] Change the interval for dependabot updates to monthly (#3769) --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ed8f4a432b..234b07e766 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,4 +6,4 @@ updates: - package-ecosystem: 'github-actions' directory: '/' schedule: - interval: 'daily' + interval: 'monthly' From b4391bcb77d28f261e45807f3b7adb4b2e086a2f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 14 Sep 2024 16:11:14 -0700 Subject: [PATCH 710/826] Fixed broken linkes in hints file. (#3773) --- exercises/concept/cater-waiter/.docs/hints.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/exercises/concept/cater-waiter/.docs/hints.md b/exercises/concept/cater-waiter/.docs/hints.md index 89f51753bc..c8b5c0bade 100644 --- a/exercises/concept/cater-waiter/.docs/hints.md +++ b/exercises/concept/cater-waiter/.docs/hints.md @@ -10,52 +10,52 @@ ## 1. Clean up Dish Ingredients -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- Remember: [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- Remember: [concept: tuples](/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. ## 2. Cocktails and Mocktails - A `set` is _disjoint_ from another set if the two sets share no elements. -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- In Python, [strings:python/strings](https://exercism.lol/tracks/python/concepts/strings) can be concatenated with the `+` sign. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- In Python, [concept: strings](/tracks/python/concepts/strings) can be concatenated with the `+` sign. ## 3. Categorize Dishes -- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the available meal categories might be useful here. +- Using [concept: loops](/tracks/python/concepts/loops) to iterate through the available meal categories might be useful here. - If all the elements of `` are contained within ``, then ` <= `. - The method equivalent of `<=` is `.issubset()` -- [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can contain any data type, including other tuples. Tuples can be formed using `(, )` or via the `tuple()` constructor. -- Elements within [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number. -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- [strings:python/strings](https://exercism.lol/tracks/python/concepts/strings) can be concatenated with the `+` sign. +- [concept: tuples](/tracks/python/concepts/tuples) can contain any data type, including other tuples. Tuples can be formed using `(, )` or via the `tuple()` constructor. +- Elements within [concept: tuples](/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- [concept: strings](/tracks/python/concepts/strings) can be concatenated with the `+` sign. ## 4. Label Allergens and Restricted Foods - A set _intersection_ are the elements shared between `` and ``. - The set method equivalent of `&` is `.intersection()` -- Elements within [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number. -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. +- Elements within [concept: tuples](/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- [concept: tuples](/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. ## 5. Compile a "Master List" of Ingredients - A set _union_ is where ` and `` are combined into a single `set` - The set method equivalent of `|` is `.union()` -- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the various dishes might be useful here. +- Using [concept: loops](/tracks/python/concepts/loops) to iterate through the various dishes might be useful here. ## 6. Pull out Appetizers for Passing on Trays - A set _difference_ is where the elements of `` are removed from ``, e.g. ` - `. - The set method equivalent of `-` is `.difference()` -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- The [list:python/lists](https://exercism.lol/tracks/python/concepts/lists) constructor can take any [iterable][iterable] as an argument. Sets are iterable. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- The [concept: list](/tracks/python/concepts/lists) constructor can take any [iterable][iterable] as an argument. Sets are iterable. ## 7. Find Ingredients Used in Only One Recipe - A set _symmetric difference_ is where elements appear in `` or ``, but not **_both_** sets. - A set _symmetric difference_ is the same as subtracting the `set` _intersection_ from the `set` _union_, e.g. `( | ) - ( & )` - A _symmetric difference_ of more than two `sets` will include elements that are repeated more than two times across the input `sets`. To remove these cross-set repeated elements, the _intersections_ between set pairs needs to be subtracted from the symmetric difference. -- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the various dishes might be useful here. +- Using [concept: loops](/tracks/python/concepts/loops) to iterate through the various dishes might be useful here. [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable From c4925ea95235d0e968e80675878948f3676e6dc8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 17 Sep 2024 14:05:28 -0700 Subject: [PATCH 711/826] Modified instructions to use add_item instead of add_items. (#3774) --- .../concept/mecha-munch-management/.docs/instructions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index 13d88f39fa..802366aab4 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -13,15 +13,15 @@ If a user wants to add 2 Oranges, 'Oranges' will appear twice in the input itera If the user already has the item in their cart, the cart quantity should be increased by 1. If the item is _new_ to the cart, it should be added with a quantity of 1. -Create the function `add_items(, )` that takes a cart dictionary and any list-like iterable of items to add as arguments. +Create the function `add_item(, )` that takes a cart dictionary and any list-like iterable of items to add as arguments. It should return a new/updated shopping cart dictionary for the user. ```python ->>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, +>>> add_item({'Banana': 3, 'Apple': 2, 'Orange': 1}, ('Apple', 'Apple', 'Orange', 'Apple', 'Banana')) {'Banana': 4, 'Apple': 5, 'Orange': 2} ->>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, +>>> add_item({'Banana': 3, 'Apple': 2, 'Orange': 1}, ['Banana', 'Orange', 'Blueberries', 'Banana']) {'Banana': 5, 'Apple': 2, 'Orange': 2, 'Blueberries': 1} ``` From 6c78db5cc71e319862daed33460006f44199d53c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 24 Sep 2024 14:50:37 -0700 Subject: [PATCH 712/826] Add variable defs to cater-waiter introduction. (#3775) --- .../cater-waiter/.docs/introduction.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 94a0b4c766..02addaab96 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -126,6 +126,27 @@ There is no operator equivalent: ```python +# Both mammals and additional_animals are lists. +>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant'] +>>> additional_animals = ['pangolin', 'panda', 'parrot', + 'lemur', 'tiger', 'pangolin'] + +# Animals is a dict. +>>> animals = {'chicken': 'white', + 'sparrow': 'grey', + 'eagle': 'brown and white', + 'albatross': 'grey and white', + 'crow': 'black', + 'elephant': 'grey', + 'dog': 'rust', + 'cow': 'black and white', + 'tiger': 'orange and black', + 'cat': 'grey', + 'squirrel': 'black'} + +# Birds is a set. +>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} + # Mammals and birds don't share any elements. >>> birds.isdisjoint(mammals) True From 2a09812b26577fd37fc4664630a78282af8b5a60 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 27 Sep 2024 06:29:41 -0700 Subject: [PATCH 713/826] corrected refrence to python internal sort method from timsort to powersort. (#3776) --- concepts/list-methods/about.md | 23 +++++++++++++++---- .../.docs/introduction.md | 13 +++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/concepts/list-methods/about.md b/concepts/list-methods/about.md index 3a5132fd0a..f0eb704808 100644 --- a/concepts/list-methods/about.md +++ b/concepts/list-methods/about.md @@ -136,10 +136,22 @@ The order of list elements can be reversed _**in place**_ with `.reverse( [3, 2, 1] ``` -List elements can be sorted _**in place**_ using `.sort()`. - Internally, Python uses [`Timsort`][timsort] to arrange the elements. - The default order is _ascending_. - The Python docs have [additional tips and techniques for sorting][sorting how to] `lists` effectively. +A list can be re-ordered _**in place**_ with the help of `.sort()`. +Default sort order is _ascending_ from the left. +The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. + + +~~~~exercism/note + From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward. +You can read more details and discussion on the change from the core Python team in the GitHub [issue 78742][78742]. + +For technical details on the algorithm, see the J. Ian Munro and Sebastian Wild paper [Nearly-Optimal Mergesorts: Fast, Practical Sorting Methods That Optimally Adapt to Existing Runs][nearly-optimal-mergesorts] + +[78742]: https://github.com/python/cpython/issues/78742 +[nearly-optimal-mergesorts]: https://arxiv.org/abs/1805.04154 +[powersort]: https://www.wild-inter.net/publications/munro-wild-2018 +[timsort]: https://en.wikipedia.org/wiki/Timsort +~~~~ ```python @@ -256,5 +268,6 @@ For a detailed explanation of names, values, list, and nested list behavior, tak [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings [sorted]: https://docs.python.org/3/library/functions.html#sorted [sorting how to]: https://docs.python.org/3/howto/sorting.html -[timsort]: https://en.wikipedia.org/wiki/Timsort [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple + + diff --git a/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md b/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md index 764de5fc54..ce593407a5 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md +++ b/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md @@ -151,9 +151,15 @@ The `.reverse()` method will reverse the order of elements **in-place**. A list can be re-ordered _**in place**_ with the help of `.sort()`. - Internally, Python uses [`Timsort`][timsort] to arrange the list. - Default order is _ascending_ from the left. - The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. +Default sort order is _ascending_ from the left. +The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. + +~~~~exercism/note + From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward. + +[powersort]: https://www.wild-inter.net/publications/munro-wild-2018 +[timsort]: https://en.wikipedia.org/wiki/Timsort +~~~~ ```python @@ -244,5 +250,4 @@ ValueError: 10 is not in list [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings [sorted]: https://docs.python.org/3/library/functions.html#sorted [sorting how to]: https://docs.python.org/3/howto/sorting.html -[timsort]: https://en.wikipedia.org/wiki/Timsort [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple From 7e38ff11718198bd95d758b2b2bba11db32eef4f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 27 Sep 2024 09:17:15 -0700 Subject: [PATCH 714/826] [Chaitanas Colossal Coaster]: Removed "effectively" from HOWTO sentence. (#3777) * Removed effectively from HOWTO sentence. * Resorted reflinks. --- concepts/list-methods/about.md | 7 +++---- .../chaitanas-colossal-coaster/.docs/introduction.md | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/concepts/list-methods/about.md b/concepts/list-methods/about.md index f0eb704808..1c9686360d 100644 --- a/concepts/list-methods/about.md +++ b/concepts/list-methods/about.md @@ -136,9 +136,9 @@ The order of list elements can be reversed _**in place**_ with `.reverse( [3, 2, 1] ``` -A list can be re-ordered _**in place**_ with the help of `.sort()`. +A list can be re-ordered _**in place**_ with the help of [`.sort()`][sort]. Default sort order is _ascending_ from the left. -The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. +The Python docs offer [additional tips and techniques for sorting][sorting how to]. ~~~~exercism/note @@ -266,8 +266,7 @@ For a detailed explanation of names, values, list, and nested list behavior, tak [set]: https://docs.python.org/3/library/stdtypes.html#set [shallow vs deep]: https://realpython.com/copying-python-objects/ [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings +[sort]: https://docs.python.org/3/library/stdtypes.html#list.sort [sorted]: https://docs.python.org/3/library/functions.html#sorted [sorting how to]: https://docs.python.org/3/howto/sorting.html [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple - - diff --git a/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md b/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md index ce593407a5..bfbf053752 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md +++ b/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md @@ -150,9 +150,9 @@ The `.reverse()` method will reverse the order of elements **in-place**. ``` -A list can be re-ordered _**in place**_ with the help of `.sort()`. +A list can be re-ordered _**in place**_ with the help of [`.sort()`][sort]. Default sort order is _ascending_ from the left. -The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. +The Python docs offer [additional tips and techniques for sorting][sorting how to]. ~~~~exercism/note From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward. @@ -239,7 +239,6 @@ ValueError: 10 is not in list 3 ``` - [common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations [dict]: https://docs.python.org/3/library/stdtypes.html#dict [list-methods]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists @@ -248,6 +247,7 @@ ValueError: 10 is not in list [sequence type]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range [set]: https://docs.python.org/3/library/stdtypes.html#set [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings +[sort]: https://docs.python.org/3/library/stdtypes.html#list.sort [sorted]: https://docs.python.org/3/library/functions.html#sorted [sorting how to]: https://docs.python.org/3/howto/sorting.html [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple From b6b3c4b64928901699ed5f87008614a1307b0231 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:08:47 -0700 Subject: [PATCH 715/826] Bump actions/checkout from 4.1.7 to 4.2.0 (#3778) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.7 to 4.2.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/692973e3d937129bcbf40652eb9f2f61becf3332...d632683dd7b4114ad314bca15554477dd762a938) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index a2257ad173..c895b2ae2a 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Set up Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index f8d9227f3c..d4434c808b 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index b9600cc8b9..9faae798e2 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Run test-runner run: docker compose run test-runner From 6a2ddfaae786156329599c56e2ccccf9e077bc98 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 7 Oct 2024 16:45:59 -0700 Subject: [PATCH 716/826] Typos and emdash changes. (#3780) --- concepts/sets/about.md | 6 +++--- concepts/sets/introduction.md | 4 ++-- exercises/concept/cater-waiter/.docs/instructions.md | 4 ++-- exercises/concept/cater-waiter/.docs/introduction.md | 8 ++++---- exercises/concept/cater-waiter/sets.py | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 321c5116c9..058be5c7de 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -1,8 +1,8 @@ # Sets A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. -Set members must be distinct -- duplicate items are not allowed. -They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Set members must be distinct — duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. @@ -360,7 +360,7 @@ The operator version of this method is ` - - - 'Goose Berries', 'Strawberries'} # Operators require sets. ->>> berries_and_veggies - just_berries +>>> berries_and_veggies - berries {'Artichokes','Asparagus','Broccoli','Kale', 'Ramps','Rhubarb','Walking Onions','Watercress'} ``` diff --git a/concepts/sets/introduction.md b/concepts/sets/introduction.md index 4c264f0903..5d66b6a8ad 100644 --- a/concepts/sets/introduction.md +++ b/concepts/sets/introduction.md @@ -1,8 +1,8 @@ # Sets A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. -Set members must be distinct -- duplicate items are not allowed. -They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Set members must be distinct — duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. diff --git a/exercises/concept/cater-waiter/.docs/instructions.md b/exercises/concept/cater-waiter/.docs/instructions.md index 696883c3c9..b0a9e1f855 100644 --- a/exercises/concept/cater-waiter/.docs/instructions.md +++ b/exercises/concept/cater-waiter/.docs/instructions.md @@ -4,7 +4,7 @@ You and your business partners operate a small catering company. You've just agr ## 1. Clean up Dish Ingredients -The event recipes were added from various sources and their ingredients appear to have duplicate (_or more_) entries -- you don't want to end up purchasing excess items! +The event recipes were added from various sources and their ingredients appear to have duplicate (_or more_) entries — you don't want to end up purchasing excess items! Before the shopping and cooking can commence, each dish's ingredient list needs to be "cleaned". Implement the `clean_ingredients(, )` function that takes the name of a dish and a `list` of ingredients. @@ -138,5 +138,5 @@ from sets_categories_data import example_dishes, EXAMPLE_INTERSECTION >>> singleton_ingredients(example_dishes, EXAMPLE_INTERSECTION) ... -{'vegetable oil', 'vegetable stock', 'barley malt', 'tofu', 'fresh basil', 'lemon', 'ginger', 'honey', 'spaghetti', 'cornstarch', 'yeast', 'red onion', 'breadcrumbs', 'mixed herbs', 'garlic powder', 'celeriac', 'lemon zest', 'sunflower oil', 'mushrooms', 'silken tofu', 'smoked tofu', 'bell pepper', 'cashews', 'oregano', 'tomatoes', 'parsley', 'red pepper flakes', 'rosemary'} +{'garlic powder', 'sunflower oil', 'mixed herbs', 'cornstarch', 'celeriac', 'honey', 'mushrooms', 'bell pepper', 'rosemary', 'parsley', 'lemon', 'yeast', 'vegetable oil', 'vegetable stock', 'silken tofu', 'tofu', 'cashews', 'lemon zest', 'smoked tofu', 'spaghetti', 'ginger', 'breadcrumbs', 'tomatoes', 'barley malt', 'red pepper flakes', 'oregano', 'red onion', 'fresh basil'} ``` diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 02addaab96..6a7993bd8a 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -1,8 +1,8 @@ # Sets -A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. -Set members must be distinct -- duplicate items are not allowed. -They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +A [`set`][type-set] is a _mutable_ and unordered_ collection of [_hashable_][hashable] objects. +Set members must be distinct — duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. @@ -332,7 +332,7 @@ The operator version of this method is ` - - - 'Goose Berries', 'Strawberries'} # Operators require sets. ->>> berries_and_veggies - just_berries +>>> berries_and_veggies - berries {'Artichokes','Asparagus','Broccoli','Kale', 'Ramps','Rhubarb','Walking Onions','Watercress'} ``` diff --git a/exercises/concept/cater-waiter/sets.py b/exercises/concept/cater-waiter/sets.py index b0202e6a5f..e726e3d664 100644 --- a/exercises/concept/cater-waiter/sets.py +++ b/exercises/concept/cater-waiter/sets.py @@ -43,7 +43,7 @@ def categorize_dish(dish_name, dish_ingredients): """Categorize `dish_name` based on `dish_ingredients`. :param dish_name: str - dish to be categorized. - :param dish_ingredients: list - ingredients for the dish. + :param dish_ingredients: set - ingredients for the dish. :return: str - the dish name appended with ": ". This function should return a string with the `dish name: ` (which meal category the dish belongs to). From b13c61ea36aac201be76c0ec6279b44370e74071 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 8 Oct 2024 00:50:36 -0700 Subject: [PATCH 717/826] Added missing italics underscore back into file. (#3781) --- exercises/concept/cater-waiter/.docs/introduction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 6a7993bd8a..044432b5c2 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -1,6 +1,7 @@ # Sets -A [`set`][type-set] is a _mutable_ and unordered_ collection of [_hashable_][hashable] objects. + +A [set][type-set] is a _mutable_ and _unordered_ collection of [_hasable_][hashable] objects. Set members must be distinct — duplicate items are not allowed. They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. From 3dcbf4cb7cb73af4eef54a07fa03f951d38fa16b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 8 Oct 2024 12:02:33 -0700 Subject: [PATCH 718/826] Fixed dict.items refrences and text. (#3782) --- .../about.md | 17 ++++++------- .../locomotive-engineer/.docs/introduction.md | 24 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 1cec2f92ec..787e2ef08e 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -129,9 +129,9 @@ ValueError: too many values to unpack (expected 1) ### Unpacking a list/tuple with `*` -When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the "leftover" values. +When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture "leftover" values. This is clearer than slicing the `list`/`tuple` (_which in some situations is less readable_). -For example, we can extract the first element and then assign the remaining values into a new `list` without the first element: +For example, we can extract the first element and pack the remaining values into a new `list` without the first element: ```python >>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] @@ -169,7 +169,7 @@ We can also use `*` in deep unpacking: ### Unpacking a dictionary -[Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`. +[Unpacking a dictionary][packing and unpacking] is a bit different from unpacking a `list`/`tuple`. Iteration over dictionaries defaults to the **keys**. So when unpacking a `dict`, you can only unpack the **keys** and not the **values**: @@ -180,7 +180,7 @@ So when unpacking a `dict`, you can only unpack the **keys** and not the **value "apple" ``` -If you want to unpack the values then you can use the `values()` method: +If you want to unpack the values then you can use the `.values()` method: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -189,9 +189,9 @@ If you want to unpack the values then you can use the `values()` method: 6 ``` -If both **keys** and **values** are needed, use the `items()` method. -Using `items()` will generate tuples with **key-value** pairs. -This is because of [`dict.items()` generates an iterable with key-value `tuples`][items]. +If both **keys** and **values** are needed, use the [`.items()`][items] method. +`.items()` generates an [iterable view][view-objects] containing **key-value** pairs. +These can be unpacked into a `tuple`: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -367,8 +367,9 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the ``` [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ -[items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ +[items]: https://docs.python.org/3/library/stdtypes.html#dict.items [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp +[view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 66d9ba1581..798a334aeb 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -9,6 +9,7 @@ The special operators `*` and `**` are often used in unpacking contexts and with `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. ~~~~ + ## Multiple assignment In multiple assignment, the number of variables on the left side of the assignment operator (`=`) must match the number of values on the right side. @@ -55,6 +56,7 @@ For example: Since `tuples` are immutable, you can't swap elements in a `tuple`. + ## Unpacking ~~~~exercism/note @@ -80,9 +82,10 @@ If there are values that are not needed then you can use `_` to flag them: "cherry" ``` + ### Deep unpacking -Unpacking and assigning values from a `list`/`tuple` inside of a `list` or `tuple` (_also known as nested lists/tuples_), works in the same way a shallow unpacking does, but often needs qualifiers to clarify the values context or position: +Unpacking and assigning values from a `list`/`tuple` enclosed inside a `list` or `tuple` (_also known as nested lists/tuples_) works in the same way a shallow unpacking does — but often needs qualifiers to clarify the context or position: ```python >>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] @@ -119,7 +122,7 @@ ValueError: too many values to unpack (expected 1) When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the "leftover" values. This is clearer than slicing the `list`/`tuple` (_which in some situations is less readable_). -For example, we can extract the first element and then assign the remaining values into a new `list` without the first element: +For example, the first element can be extracted and then the remaining values can be placed into a new `list` without the first element: ```python >>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] @@ -157,7 +160,7 @@ We can also use `*` in deep unpacking: ### Unpacking a dictionary -[Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`. +[Unpacking a dictionary][packing and unpacking] is a bit different from unpacking a `list`/`tuple`. Iteration over dictionaries defaults to the **keys**. So when unpacking a `dict`, you can only unpack the **keys** and not the **values**: @@ -168,7 +171,7 @@ So when unpacking a `dict`, you can only unpack the **keys** and not the **value "apple" ``` -If you want to unpack the values then you can use the `values()` method: +If you want to unpack the values then you can use the `.values()` method: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -177,9 +180,9 @@ If you want to unpack the values then you can use the `values()` method: 6 ``` -If both **keys** and **values** are needed, use the `items()` method. -Using `items()` will generate tuples with **key-value** pairs. -This is because of [`dict.items()` generates an iterable with key-value `tuples`][items]. +If both **keys** and **values** are needed, use the [`.items()`][items] method. +`.items()` generates an [iterable view][view-objects] containing **key-value** pairs. +These can be unpacked into a `tuple`: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -238,8 +241,8 @@ This will pack all **key**-**value** pairs from one dictionary into another dict ### Packing with function parameters When you create a function that accepts an arbitrary number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] in the function definition. -`*args` is used to pack an arbitrary number of positional (non-keyworded) arguments and -`**kwargs` is used to pack an arbitrary number of keyword arguments. +`*args` is used to pack an arbitrary number of positional (_non-keyword_) arguments as a `tuple` and +`**kwargs` is used to pack an arbitrary number of keyword arguments as a dictionary. Usage of `*args`: @@ -355,8 +358,9 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the ``` [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ -[items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ +[items]: https://docs.python.org/3/library/stdtypes.html#dict.items [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp +[view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views From fb1cb444ad75bfcf8c0d5087f4144856e439fcfc Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 11 Oct 2024 17:41:40 -0700 Subject: [PATCH 719/826] [Wordy & Wordy Approaches]: Added 6 Additional Approaches & Modified the Instruction Append for Wordy. (#3783) * Added 6 additional appraches and extended introduction for Wordy. * Corrected slug for regex with operator module approach. --- .../practice/wordy/.approaches/config.json | 46 +- .../dunder-getattribute/content.md | 78 ++-- .../.approaches/functools-reduce/content.md | 126 ++++++ .../.approaches/functools-reduce/snippet.txt | 7 + .../import-callables-from-operator/content.md | 93 ++++ .../snippet.txt | 8 + .../wordy/.approaches/introduction.md | 416 +++++++++++++++++- .../lambdas-in-a-dictionary/content.md | 100 +++++ .../lambdas-in-a-dictionary/snippet.txt | 6 + .../wordy/.approaches/recursion/content.md | 266 +++++++++++ .../wordy/.approaches/recursion/snippet.txt | 8 + .../regex-with-operator-module/content.md | 98 +++++ .../regex-with-operator-module/snippet.txt | 8 + .../string-list-and-dict-methods/content.md | 161 +++++++ .../string-list-and-dict-methods/snippet.txt | 8 + .../wordy/.docs/instructions.append.md | 26 +- 16 files changed, 1392 insertions(+), 63 deletions(-) create mode 100644 exercises/practice/wordy/.approaches/functools-reduce/content.md create mode 100644 exercises/practice/wordy/.approaches/functools-reduce/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/import-callables-from-operator/content.md create mode 100644 exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md create mode 100644 exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/recursion/content.md create mode 100644 exercises/practice/wordy/.approaches/recursion/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/regex-with-operator-module/content.md create mode 100644 exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md create mode 100644 exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt diff --git a/exercises/practice/wordy/.approaches/config.json b/exercises/practice/wordy/.approaches/config.json index ed71a84650..79a3a114ba 100644 --- a/exercises/practice/wordy/.approaches/config.json +++ b/exercises/practice/wordy/.approaches/config.json @@ -1,10 +1,52 @@ { "introduction": { - "authors": ["bobahop"], - "contributors": [] + "authors": ["BethanyG"], + "contributors": ["bobahop"] }, "approaches": [ { + "uuid": "4eeb0638-671a-4289-a83c-583b616dc698", + "slug": "string-list-and-dict-methods", + "title": "String, List, and Dictionary Methods", + "blurb": "Use Core Python Features to Solve Word Problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "d3ff485a-defe-42d9-b9c6-c38019221ffa", + "slug": "import-callables-from-operator", + "title": "Import Callables from the Operator Module", + "blurb": "Use Operator Module Methods to Solve Word Problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "61f44943-8a12-471b-ab15-d0d10fa4f72f", + "slug": "regex-with-operator-module", + "title": "Regex with the Operator Module", + "blurb": "Use Regex with the Callables from Operator to solve word problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "46bd15dd-cae4-4eb3-ac63-a8b631a508d1", + "slug": "lambdas-in-a-dictionary", + "title": "Lambdas in a Dictionary to Return Functions", + "blurb": "Use lambdas in a dictionary to return functions for solving word problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "2e643b88-9b76-45a1-98f4-b211919af061", + "slug": "recursion", + "title": "Recursion for iteration.", + "blurb": "Use recursion with other strategies to solve word problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "1e136304-959c-4ad1-bc4a-450d13e5f668", + "slug": "functools-reduce", + "title": "Functools.reduce for Calculation", + "blurb": "Use functools.reduce with other strategies to calculate solutions.", + "authors": ["BethanyG"] + }, + { "uuid": "d643e2b4-daee-422d-b8d3-2cad2f439db5", "slug": "dunder-getattribute", "title": "dunder with __getattribute__", diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index 76280615bc..31c42e7a2a 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -1,5 +1,6 @@ # Dunder methods with `__getattribute__` + ```python OPS = { "plus": "__add__", @@ -33,70 +34,61 @@ def answer(question): ``` -This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [dunder][dunder] methods. +This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [`dunder-methods`][dunder] methods. +Since only whole numbers are involved, the available `dunder-methods` are those for the [`int`][int] class/namespace. +The supported methods for the `int()` namespace can be found by using `print(dir(int))` or `print(int.__dict__)` in a Python terminal. +See [SO: Difference between dir() and __dict__][dir-vs-__dict__] for differences between the two. ~~~~exercism/note -They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. -They are also called magic methods. +The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of all valid attributes for an object. +The `dunder-method` [`.__dict__`](https://docs.python.org/3/reference/datamodel.html#object.__dict__) is a mapping of an objects writable attributes. ~~~~ -Since only whole numbers are involved, the dunder methods are those for [`int`][int]. -The supported methods for `int` can be found by using `print(dir(int))`. - -~~~~exercism/note -The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of valid attributes for an object. -~~~~ +The `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. +It indicates that the value should not be changed. -Python doesn't _enforce_ having real constant values, -but the `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. -It indicates that the value is not intended to be changed. - -The input question to the `answer` function is cleaned using the [`removeprefix`][removeprefix], [`removesuffix`][removesuffix], and [`strip`][strip] methods. +The input question to the `answer()` function is cleaned using the [`removeprefix`][removeprefix], [`removesuffix`][removesuffix], and [`strip`][strip] string methods. The method calls are [chained][method-chaining], so that the output from one call is the input for the next call. If the input has no characters left, -it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the [`ValueError`][value-error] for having a syntax error. +it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return a `ValueError("syntax error")`. -Next, the [`isdigit`][isdigit] method is used to see if all of the remaining characters in the input are digits. +Next, the [`isdigit`][isdigit] method is used to see if the remaining characters in the input are digits. If so, it uses the [`int()`][int-constructor] constructor to return the string as an integer. -Next, the elements in the `OPS` dictionary are iterated. -If the key name is in the input, then the [`replace()`][replace] method is used to replace the name in the input with the dunder method value. -If none of the key names are found in the input, then a `ValueError` is returned for having an unknown operation. - -At this point the input question is [`split()`][split] into a list of its words, which is then iterated while its [`len()`][len] is greater than 1. +Next, the elements in the `OPS` dictionary are iterated over. +If the key name is in the input, then the [`str.replace`][replace] method is used to replace the name in the input with the `dunder-method` value. +If none of the key names are found in the input, a `ValueError("unknown operation")` is returned. -Within a [try][exception-handling], the list is [destructured][destructure] into `x, op, y, *tail`. -If `op` is not in the supported dunder methods, it raises `ValueError("syntax error")`. -If there are any other exceptions raised in the try, `except` raises `ValueError("syntax error")` +At this point the input question is [`split()`][split] into a `list` of its words, which is then iterated over while its [`len()`][len] is greater than 1. -Next, it converts `x` to an `int` and calls the [`__getattribute__`][getattribute] for its dunder method and calls it, -passing it `y` converted to an `int`. +Within a [try-except][exception-handling] block, the list is [unpacked][unpacking] (_see also [Concept: unpacking][unpacking-and-multiple-assignment]_) into the variables `x, op, y, and *tail`. +If `op` is not in the supported `dunder-methods` dictionary, a `ValueError("syntax error")` is raised. +If there are any other exceptions raised within the `try` block, they are "caught"/ handled in the `except` clause by raising a `ValueError("syntax error")`. -It sets the list to the result of the dunder method plus the remaining elements in `*tail`. +Next, `x` is converted to an `int` and [`__getattribute__`][getattribute] is called for the `dunder-method` (`op`) to apply to `x`. +`y` is then converted to an `int` and passed as the second arguemnt to `op`. -~~~~exercism/note -The `*` prefix in `*tail` [unpacks](https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/) the `tail` list back into its elements. -This concept is also a part of [unpacking-and-multiple-assignment](https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment) concept in the syllabus. -~~~~ +Then `ret` is redefined to a `list` containing the result of the dunder method plus the remaining elements in `*tail`. When the loop exhausts, the first element of the list is selected as the function return value. +[const]: https://realpython.com/python-constants/ [dictionaries]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[dir-vs-__dict__]: https://stackoverflow.com/a/14361362 [dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python +[exception-handling]: https://docs.python.org/3/tutorial/errors.html#handling-exceptions +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ +[int-constructor]: https://docs.python.org/3/library/functions.html?#int [int]: https://docs.python.org/3/library/stdtypes.html#typesnumeric -[const]: https://realpython.com/python-constants/ -[removeprefix]: https://docs.python.org/3/library/stdtypes.html#str.removeprefix -[removesuffix]: https://docs.python.org/3/library/stdtypes.html#str.removesuffix -[strip]: https://docs.python.org/3/library/stdtypes.html#str.strip +[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit +[len]: https://docs.python.org/3/library/functions.html?#len [method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining [not]: https://docs.python.org/3/library/operator.html?#operator.__not__ -[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ -[value-error]: https://docs.python.org/3/library/exceptions.html?#ValueError -[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit -[int-constructor]: https://docs.python.org/3/library/functions.html?#int +[removeprefix]: https://docs.python.org/3/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3/library/stdtypes.html#str.removesuffix [replace]: https://docs.python.org/3/library/stdtypes.html?#str.replace [split]: https://docs.python.org/3/library/stdtypes.html?#str.split -[len]: https://docs.python.org/3/library/functions.html?#len -[exception-handling]: https://docs.python.org/3/tutorial/errors.html#handling-exceptions -[destructure]: https://riptutorial.com/python/example/14981/destructuring-assignment -[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ +[strip]: https://docs.python.org/3/library/stdtypes.html#str.strip +[unpacking]: https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/ +[unpacking-and-multiple-assignment]: https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md new file mode 100644 index 0000000000..08f21a8ad2 --- /dev/null +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -0,0 +1,126 @@ +# Functools.reduce for Calculation + + +```python +from operator import add, mul, sub +from operator import floordiv as div +from functools import reduce + + +# Define a lookup table for mathematical operations +OPERATORS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + # Check for basic validity right away, and fail out with error if not valid. + if not question.startswith( "What is") or "cubed" in question: + raise ValueError("unknown operation") + + # Using the built-in filter() to clean & split the question.. + list(filter(lambda x: + x not in ("What", "is", "by"), + question.strip("?").split())) + + # Separate candidate operators and numbers into two lists. + operations = question[1::2] + + # Convert candidate elements to int(), checking for "-". + # All other values are replaced with None. + digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + else None for element in question[::2]] + + # If there is a mis-match between operators and numbers, toss error. + if len(digits)-1 != len(operations) or None in digits: + raise ValueError("syntax error") + + # Evaluate the expression from left to right using functools.reduce(). + # Look up each operation in the operation dictionary. + return reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) +``` + +This approach replaces the `while-loop` or `recursion` used in many solutions with a call to [`functools.reduce`][functools-reduce]. +It requires that the question be separated into candidate digits and candidate operators, which is accomplished here via [list-slicing][sequence-operations] (_for some additional information on working with `lists`, see [concept: lists](/tracks/python/concepts/lists)_). + +A nested call to `filter()` and `split()` within a `list` constructor is used to clean and process the question into an initial `list` of digit and operator strings. +However, this could easily be accomplished by either using [chained][method-chaining] string methods or a `list-comprehension`: + + +```python + # Alternative 1 is chaining various string methods together. + # The wrapping () invoke implicit concatenation for the chained functions + return (question.removeprefix("What is") + .removesuffix("?") + .replace("by", "") + .strip()).split() # <-- this split() turns the string into a list. + + + # Alternative 2 to the nested calls to filter and split is to use a list-comprehension: + return [item for item in + question.strip("?").split() + if item not in ("What", "is", "by")] #<-- The [] of the comprehension invokes implicit concatenation. +``` + + +Since "valid" questions are all in the form of `digit-operator-digit` (_and so on_), it is safe to assume that every other element beginning at index 0 is a "number", and every other element beginning at index 1 is an operator. +By that definition, the operators `list` is 1 shorter in `len()` than the digits list. +Anything else (_or having None/an unknown operation in the operations list_) is a `ValueError("syntax error")`. + + +The final call to `functools.reduce` essentially performs the same steps as the `while-loop` implementation, with the `lambda-expression` passing successive items of the digits `list` to the popped and looked-up operation from the operations `list` (_made [callable][callable] by adding `()`_), until it is reduced to one number and returned. +A `try-except` is not needed here because the error scenarios are already filtered out in the `if` check right before the call to `reduce()`. + +`functools.reduce` is certainly convenient, and makes the solution much shorter. +But it is also hard to understand what is happening if you have not worked with a reduce or foldl function in the past. +It could be argued that writing the code as a `while-loop` or recursive function is easier to reason about for non-functional programmers. + + +## Variation: 1: Use a Dictionary of `lambdas` instead of importing from operator + + +The imports from operator can be swapped out for a dictionary of `lambda-expressions` (or calls to `dunder-methods`), if so desired. +The same cautions apply here as were discussed in the [lambdas in a dictionary][approach-lambdas-in-a-dictionary] approach: + + +```python +from functools import reduce + +# Define a lookup table for mathematical operations +OPERATORS = {"plus": lambda x, y: x + y, + "minus": lambda x, y: x - y, + "multiplied": lambda x, y: x * y, + "divided": lambda x, y: x / y} + +def answer(question): + + # Check for basic validity right away, and fail out with error if not valid. + if not question.startswith( "What is") or "cubed" in question: + raise ValueError("unknown operation") + + # Clean and split the question into a list for processing. + question = [item for item in + question.strip("?").split() if + item not in ("What", "is", "by")] + + # Separate candidate operators and numbers into two lists. + operations = question[1::2] + + # Convert candidate elements to int(), checking for "-". + # All other values are replaced with None. + digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + else None for element in question[::2]] + + # If there is a mis-match between operators and numbers, toss error. + if len(digits)-1 != len(operations) or None in digits: + raise ValueError("syntax error") + + # Evaluate the expression from left to right using functools.reduce(). + # Look up each operation in the operation dictionary. + result = reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) + + return result +``` + +[approach-lambdas-in-a-dictionary]: https://exercise.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary +[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining +[sequence-operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations diff --git a/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt b/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt new file mode 100644 index 0000000000..f8d5a29419 --- /dev/null +++ b/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt @@ -0,0 +1,7 @@ +OPERATORS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +operations = question[1::2] +digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + else None for element in question[::2]] +... +return reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md new file mode 100644 index 0000000000..0c2683760a --- /dev/null +++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md @@ -0,0 +1,93 @@ +# Import Callables from the Operator Module + + +```python +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = [word for word in question.split() if word != 'by'] + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + + +This approach is nearly identical to the [string, list, and dict methods][approach-string-list-and-dict-methods] approach, so it is recommended to review that before going over this one. +The two major differences are the `operator` module, and the elimination of the `if-elif-else` block. + + +The solution begins by importing basic mathematical operations as methods from the [`operator`][operator] module. +These functions (_floordiv is [aliased][aliasing] to "div"_) are stored in a dictionary that serves as a lookup table when the problems are processed. +These operations are later made [callable][callable] by using `()` after the name, and supplying arguments. + + +In `answer()`, the question is first checked for validity, cleaned, and finally split into a `list` using [`str.startswith`][startswith], [`str.removeprefix`][removeprefix]/[`str.removesuffix`][removesuffix], [strip][strip], and [split][split]. +Checks for digits and an empty string are done, and the word "by" is filtered from the equation `list` using a [`list-comprehension`][list-comprehension]. + + +The equation `list` is then processed in a `while-loop` within a [try-except][handling-exceptions] block. +The `list` is [unpacked][unpacking] (_see also [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment)_) into `x_value`, `operation`, `y_value`, and `*rest`, and reduced by looking up and calling the mathematical function in the OPERATIONS dictionary and passing in `int(x_value)` and `int(y_value)` as arguments. + + +The processing of the equation `list` continues until it is of `len()` 1, at which point the single element is returned as the answer. + + +To walk through this step-by-step, you can interact with this code on [`pythontutor.com`][pythontutor]. + + +Using a `list-comprehension` to filter out "by" can be replaced with the [`str.replace`][str-replace] method during question cleaning. +[Implicit concatenation][implicit-concatenation] can be used to improve the readability of the [chained][chaining-method-calls] method calls: + + +```python +question = (question.removeprefix("What is") + .removesuffix("?") + .replace("by", "") + .strip()) #<-- Enclosing () means these lines are automatically joined by the interpreter. +``` + + +The call to `str.replace` could instead be chained to the call to `split` when creating the equation `list`: + + +```python +equation = question.replace("by", "").split() +``` + +[aliasing]: https://mimo.org/glossary/python +[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ +[chaining-method-calls]: https://nikhilakki.in/understanding-method-chaining-in-python +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[implicit-concatenation]: https://docs.python.org/3/reference/lexical_analysis.html#implicit-line-joining +[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[operator]: https://docs.python.org/3/library/operator.html#module-operator +[pythontutor]: https://pythontutor.com/render.html#code=from%20operator%20import%20add,%20mul,%20sub%0Afrom%20operator%20import%20floordiv%20as%20div%0A%0AOPERATIONS%20%3D%20%7B%22plus%22%3A%20add,%20%22minus%22%3A%20sub,%20%22multiplied%22%3A%20mul,%20%22divided%22%3A%20div%7D%0A%0Adef%20answer%28question%29%3A%0A%20%20%20%20if%20not%20question.startswith%28%22What%20is%22%29%20or%20%22cubed%22%20in%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22unknown%20operation%22%29%0A%20%20%20%20%0A%20%20%20%20question%20%3D%20question.removeprefix%28%22What%20is%22%29.removesuffix%28%22%3F%22%29.strip%28%29%0A%0A%20%20%20%20if%20question.isdigit%28%29%3A%20%0A%20%20%20%20%20%20%20%20return%20int%28question%29%0A%20%20%20%20%0A%20%20%20%20if%20not%20question%3A%20%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%20%20%20%20%0A%20%20%20%20equation%20%3D%20%5Bword%20for%20word%20in%20question.split%28%29%20if%20word%20!%3D%20'by'%5D%0A%20%20%20%20%0A%20%20%20%20while%20len%28equation%29%20%3E%201%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20x_value,%20operation,%20y_value,%20*rest%20%3D%20equation%0A%20%20%20%20%20%20%20%20%20%20%20%20equation%20%3D%20%5BOPERATIONS%5Boperation%5D%28int%28x_value%29,%20int%28y_value%29%29,%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*rest%5D%0A%20%20%20%20%20%20%20%20except%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%20%20%20%20%0A%20%20%20%20return%20equation%5B0%5D%0A%20%20%20%20%0Aprint%28answer%28%22What%20is%202%20plus%202%20plus%203%3F%22%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false +[removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix +[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split +[startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith +[str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace +[strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[unpacking]: https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/ \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt b/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt new file mode 100644 index 0000000000..d5cb5a1354 --- /dev/null +++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt @@ -0,0 +1,8 @@ +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} +while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)),*rest] + except: + raise ValueError("syntax error") +return equation[0] \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 9faba81cbc..ae8900e08f 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -1,22 +1,367 @@ # Introduction -There are various ways to solve Wordy. -Using [`eval`][eval] is a [convenient but potentially dangerous][eval-danger] approach. -Another approach could replace the operation words with [dunder][dunder] methods. +The objective of the Wordy exercise is to parse and evaluate small/simple mathematical word problems, returning the result as an integer. +These problems do not require complex or [PEMDAS][PEMDAS]-based evaluation and are instead processed from left-to-right _in sequence_. +This means that for some of the test cases, the solution will not be the same as if the word problem was evaluated like a traditional math problem. -~~~~exercism/note -They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. -They are also called magic methods. -~~~~ -The dunder methods can be called by using the [`__getattribute__`][getattribute] method for [`int`][int]. +## General Guidance -## General guidance +The key to a Wordy solution is to remove the "question" portion of the sentence (_"What is", "?"_) and process the remaining words between numbers as [operators][mathematical operators]. +If a single number remains after removing the "question", it should be converted to an [`int`][int] and returned as the answer. +Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a [`ValueError`][value-error] with a message. +This includes any "extra" spaces between numbers. + +One way to reduce the number of `raise` statements/ `ValueError`s needed in the code is to determine if a problem is a "valid" question _before_ proceeding to parsing and calculation. +As shown in various approaches, there are multiple strategies for validating questions, with no one "canonical" solution. +One very effective approach is to check if a question starts with "What is", ends with "?", and includes only valid operations. +That could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. + +There are various Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. +For cleaning the "question" portion of the problem, [`str.removeprefix`][removeprefix] and +[`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful: + + +```python +>>> 'Supercalifragilisticexpialidocious'.removeprefix('Super') +'califragilisticexpialidocious' + +>>> 'Supercalifragilisticexpialidocious'.removesuffix('expialidocious') +'Supercalifragilistic' + + +#The two methods can be chained to remove both a suffix and prefix in one line. +>>> 'Supercalifragilisticexpialidocious'.removesuffix('expialidocious').removeprefix('Super') +'califragilistic' +``` + + +You can also use [`str.startswith`][startswith] and [`str.endswith`][endswith] in conjunction with [string slicing][sequence-operations] for cleaning: + + +```python +>>> if 'Supercalifragilisticexpialidocious'.startswith('Super'): + new_string = 'Supercalifragilisticexpialidocious'[5:] +>>> new_string +'califragilisticexpialidocious' + + +>>> if new_string.endswith('expialidocious'): + new_string = new_string[:15] +>>> new_string +'califragilistic' +``` + + +Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.index`][index] with string slicing could be used to clean up the initial word problem. +A [regex][regex] could also be used to process the question, but might be considered overkill given the fixed nature of the prefix/suffix and operations. +Finally, [`str.strip`][strip] and its variants are very useful for cleaning up any leftover leading or trailing whitespace. + +Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient iteration, although other strategies are also used. + +For math operations, many solutions involve importing and using methods from the [operator][operator] module in combination with different looping, parsing, and substitution strategies. +Some solutions use either [lambda][lambdas] expressions or [dunder/"special" methods][dunder-methods] to replace words with arithmetic operations. +However, the exercise can be solved without using `operator`, `lambdas`, or `dunder-methods`. + +Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach. +It is also entirely unnecessary, as the other methods described here are safer and equally performant. + + +## Approach: String, List, and Dictionary Methods + + +```python +OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'} + + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if not question: + raise ValueError("syntax error") + + if question.isdigit(): + return int(question) + + formula = [] + for operation in question.split(): + if operation == 'by': + continue + else: + formula.append(OPERATIONS.get(operation, operation)) + + while len(formula) > 1: + try: + x_value = int(formula[0]) + symbol = formula[1] + y_value = int(formula[2]) + remainder = formula[3:] + + if symbol == "+": + formula = [x_value + y_value] + remainder + elif symbol == "-": + formula = [x_value - y_value] + remainder + elif symbol == "*": + formula = [x_value * y_value] + remainder + elif symbol == "/": + formula = [x_value / y_value] + remainder + else: + raise ValueError("syntax error") + except: + raise ValueError("syntax error") + + return formula[0] +``` + +This approach uses only data structures and methods (_[dict][dict], [dict.get()][dict-get] and [list()][list]_) from core Python, and does not import any extra modules. +It may have more lines of code than average, but it is clear to follow and fairly straightforward to reason about. +It does use a [try-except][handling-exceptions] block for handling unknown operators. +As an alternative to the `formula` loop-append, a [list-comprehension][list-comprehension] can be used to create the initial parsed formula. + +For more details and variations, read the [String, List and Dictionary Methods][approach-string-list-and-dict-methods] approach. + + +## Approach: Import Callables from the Operator Module + + +```python +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = [word for word in question.split() if word != 'by'] + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + +This solution imports methods from the `operator` module, and uses them in a dictionary/lookup map. +Like the first approach, it uses a [try-except][handling-exceptions] block for handling unknown operators. + It also uses a [list-comprehension][list-comprehension] to create the parsed "formula" and employs [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment). + +For more details and options, take a look at the [Import Callables from the Operator Module][approach-import-callables-from-operator] approach. + + +## Approach: Regex and the Operator Module + + +```python +import re +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied by": mul, "divided by": div} +REGEX = { + 'number': re.compile(r'-?\d+'), + 'operator': re.compile(f'(?:{"|".join(OPERATIONS)})\\b') +} + + +def get_number(question): + pattern = REGEX['number'].match(question) + if not pattern: + raise ValueError("syntax error") + return [question.removeprefix(pattern.group(0)).lstrip(), + int(pattern.group(0))] + +def get_operation(question): + pattern = REGEX['operator'].match(question) + if not pattern: + raise ValueError("unknown operation") + return [question.removeprefix(pattern.group(0)).lstrip(), + OPERATIONS[pattern.group(0)]] + +def answer(question): + prefix = "What is" + if not question.startswith(prefix): + raise ValueError("unknown operation") + + question = question.removesuffix("?").removeprefix(prefix).lstrip() + question, result = get_number(question) + + while len(question) > 0: + if REGEX['number'].match(question): + raise ValueError("syntax error") + + question, operation = get_operation(question) + question, num = get_number(question) + + result = operation(result, num) + + return result +``` + + +This approach uses a dictionary of regex patterns for matching numbers and operators, paired with a dictionary of operations imported from the `operator` module. +It pulls number and operator processing out into separate functions and uses a while loop in `answer()` to evaluate the word problem. +It also uses multiple assignment for various variables. +It is longer than some solutions, but clearer and potentially easier to maintain due to the separate `get_operation()` and `get_number()` functions. + +For more details, take a look at the [regex-with-operator-module][approach-regex-with-operator-module] approach. + + +## Approach: Lambdas in a Dictionary to return Functions + + +```python +OPERATIONS = { + 'minus': lambda a, b: a - b, + 'plus': lambda a, b: a + b, + 'multiplied': lambda a, b: a * b, + 'divided': lambda a, b: a / b + } + + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = [word for word in question.split() if word != 'by'] + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + + +Rather than import methods from the `operator` module, this approach defines a series of [`lambda expressions`][lambdas] in the OPERATIONS dictionary. +These `lambdas` then return a function that takes two numbers as arguments, returning the result. + +One drawback of this strategy over using named functions or methods from `operator` is the lack of debugging information should something go wrong with evaluation. +Lambda expressions are all named `"lambda"` in stack traces, so it becomes less clear where an error is coming from if you have a number of lambda expressions within a large program. +Since this is not a large program, debugging these `lambdas` is fairly straightforward. +These "hand-crafted" `lambdas` could also introduce a mathematical error, although for the simple problems in Wordy, this is a fairly small consideration. + +For more details, take a look at the [Lambdas in a Dictionary][approach-lambdas-in-a-dictionary] approach. + + +## Approach: Recursion + + +```python +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + return calculate(clean(question)) + +def clean(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + return (question.removeprefix("What is") + .removesuffix("?") + .replace("by", "") + .strip()).split() + +def calculate(equation): + if len(equation) == 1: + return int(equation[0]) + else: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), + int(y_value)), *rest] + except: + raise ValueError("syntax error") + + return calculate(equation) +``` + + +Like previous approaches that substitute methods from `operator` for `lambdas` or `list-comprehensions` for `loops` that append to a `list` -- `recursion` can be substituted for the `while-loop` that many solutions use to process a parsed word problem. +Depending on who is reading the code, `recursion` may or may not be easier to reason about. +It may also be more (_or less!_) performant than using a `while-loop` or `functools.reduce` (_see below_), depending on how the various cleaning and error-checking actions are performed. + +The dictionary in this example could use functions from `operator`, `lambdas`, `dunder-methods`, or other strategies -- as long as they can be applied in the `calculate()` function. + +For more details, take a look at the [recursion][approach-recursion] approach. + + +## Approach: functools.reduce() + + +```python +from operator import add, mul, sub +from operator import floordiv as div +from functools import reduce + + +OPERATORS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + if not question.startswith( "What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = list(filter(lambda x: + x not in ("What", "is", "by"), + question.strip("?").split())) + + operations = question[1::2] + digits = [int(element) if (element.isdigit() or + element[1:].isdigit()) else None for + element in question[::2]] + + if len(digits)-1 != len(operations) or None in digits: + raise ValueError("syntax error") + + result = reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) + + return result +``` + + +This approach replaces the `while-loop` used in many solutions (_or the `recursion` strategy outlined in the approach above_) with a call to [`functools.reduce`][functools-reduce]. +It also employs a lookup dictionary for methods imported from the `operator` module, as well as a `list-comprehension`, the built-in [`filter`][filter] function, and multiple string [slices][sequence-operations]. +If desired, the `operator` imports can be replaced with a dictionary of `lambda` expressions or `dunder-methods`. + +This solution may be a little less clear to follow or reason about due to the slicing syntax and the particular syntax of both `filter` and `fuctools.reduce`. + +For more details and variations, take a look at the [functools.reduce for Calculation][approach-functools-reduce] approach. -Parsing should verify that the expression in words can be translated to a valid mathematical expression. ## Approach: Dunder methods with `__getattribute__` + ```python OPS = { "plus": "__add__", @@ -50,11 +395,52 @@ def answer(question): ``` -For more information, check the [dunder method with `__getattribute__` approach][approach-dunder-getattribute]. +This approach uses the [`dunder methods`][dunder-methods] / ["special methods"][special-methods] / "magic methods" associated with the `int()` class, using the `dunder-method` called [`.__getattribute__`][getattribute] to find the [callable][callable] operation in the `int()` class [namespace][namespace] / dictionary. +This works because the operators for basic math (_"+, -, *, /, //, %, **"_) have been implemented as callable methods for all integers (_as well as floats and other number types_) and are automatically loaded when the Python interpreter is loaded. + +As described in the first link, it is considered bad form to directly call a `dunder method` (_there are some exceptions_), as they are intended mostly for internal Python use, user-defined class customization, and operator overloading (_a specific form of class-customization_). + +This is why the `operator` module exists - as a vehicle for providing callable methods for basic math when **not** overloading or customizing class functionality. +For more detail on this solution, take a look at the [dunder method with `__getattribute__` approach][approach-dunder-getattribute]. + + +[PEMDAS]: https://www.mathnasium.com/math-centers/eagan/news/what-pemdas-e +[approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute +[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/wordy/approaches/functools-reduce +[approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator +[approach-lambdas-in-a-dictionary]: https://exercise.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary +[approach-recursion]: https://exercise.org/tracks/python/exercises/wordy/approaches/recursion +[approach-regex-with-operator-module]: https://exercise.org/tracks/python/exercises/wordy/approaches/regex-with-operator-module +[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ +[dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get +[dict]: https://docs.python.org/3/library/stdtypes.html#dict +[dunder-methods]: https://www.pythonmorsels.com/what-are-dunder-methods/?watch +[endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith +[eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu +[eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html [eval]: https://docs.python.org/3/library/functions.html?#eval -[eval-danger]: https://diveintopython3.net/advanced-iterators.html#eval -[dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python -[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ +[find]: https://docs.python.org/3.9/library/stdtypes.html#str.find +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[getattribute]: https://docs.python.org/3/reference/datamodel.html#object.__getattribute__ +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[index]: https://docs.python.org/3.9/library/stdtypes.html#str.index [int]: https://docs.python.org/3/library/stdtypes.html#typesnumeric -[approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute +[lambdas]: https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression +[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[list]: https://docs.python.org/3/library/stdtypes.html#list +[mathematical operators]: https://www.w3schools.com/python/gloss_python_arithmetic_operators.asp +[namespace]: https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces +[operator]: https://docs.python.org/3/library/operator.html#module-operator +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[regex]: https://docs.python.org/3/library/re.html#module-re +[removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix +[rfind]: https://docs.python.org/3.9/library/stdtypes.html#str.rfind +[sequence-operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[special-methods]: https://docs.python.org/3/reference/datamodel.html#specialnames +[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split +[startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith +[strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError diff --git a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md new file mode 100644 index 0000000000..6836e7b468 --- /dev/null +++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md @@ -0,0 +1,100 @@ +# Lambdas in a Dictionary to Return Functions + + +```python +OPERATIONS = { + 'minus': lambda a, b: a - b, + 'plus': lambda a, b: a + b, + 'multiplied': lambda a, b: a * b, + 'divided': lambda a, b: a / b + } + + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = question.replace("by", "").split() + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + +This approach is nearly identical to the [string, list, and dict methods][approach-string-list-and-dict-methods] and the [import callables from the operator][approach-import-callables-from-operator] approaches, so it is recommended that you review those before going over this one. +The major difference here is the use of [`lambda expressions`][lambdas] in place of `operator` methods or string representations in the OPERATIONS dictionary. + +`lambda expressions` are small "throwaway" expressions that are simple enough to not require a formal function definition or name. +They are most commonly used in [`key functions`][key-functions], the built-ins [`map`][map] and [`filter`][filter], and in [`functools.reduce`][functools-reduce]. + `lambdas` are also often defined in areas where a function is needed for one-time use or callback but it would be onerous or confusing to create a full function definition. +The two forms are parsed identically (_they are both function definitions_), but in the case of [`lambdas`][lambda], the function name is always "lambda" and the expression cannot contain statements or annotations. + +For example, the code above could be re-written to include user-defined functions as opposed to `lambda expressions`: + + +```python +def add_(x, y): + return x + y + +def mul_(x, y): + return x * y + +def div_(x, y): + return x//y + +def sub_(x, y): + return x - y + +def answer(question): + operations = {'minus': sub_,'plus': add_,'multiplied': mul_,'divided': div_} + + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = question.replace("by", "").split() + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [operations[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + +However, this makes the code more verbose and does not improve readability. +In addition, the functions need to carry a trailing underscore to avoid potential shadowing or name conflict. +It is better and cleaner in this circumstance to use `lambda expressions` for the functions - although it could be argued that importing and using the methods from `operator` is even better and clearer. + +[approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator +[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[filter]: https://docs.python.org/3/library/functions.html#filter +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[key-functions]: https://docs.python.org/3/howto/sorting.html#key-functions +[lambda]: https://docs.python.org/3/reference/expressions.html#lambda +[lambdas]: https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression +[map]: https://docs.python.org/3/library/functions.html#map diff --git a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt new file mode 100644 index 0000000000..3769bef8c5 --- /dev/null +++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt @@ -0,0 +1,6 @@ +OPERATIONS = { + 'minus': lambda a, b: a - b, + 'plus': lambda a, b: a + b, + 'multiplied': lambda a, b: a * b, + 'divided': lambda a, b: a / b + } \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md new file mode 100644 index 0000000000..5dd0a7ba37 --- /dev/null +++ b/exercises/practice/wordy/.approaches/recursion/content.md @@ -0,0 +1,266 @@ +# Recursion + + +[Any function that can be written iteratively (_with loops_) can be written using recursion][recursion-and-iteration], and [vice-versa][recursion-is-not-a-superpower]. +A recursive strategy [may not always be obvious][looping-vs-recursion] or easy — but it is always possible. +So the `while-loop`s used in other approaches to Wordy can be re-written to use recursive calls. + +That being said, Pyton famously does not perform [tail-call optimization][tail-call-optimization], and limits recursive calls on the stack to a depth of 1000 frames, so it is important to only use recursion where you are confident that it can complete within the limit (_or something close to it_). +[Memoization][memoization] and other strategies in [dynamic programming][dynamic-programming] can help to make recursion more efficient and "shorter" in Python, but it's always good to give it careful consideration. + +Recursion works best with problem spaces that resemble trees, include [backtracking][backtracking], or become progressively smaller. + Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-firs search][dfs], and [merge sort][merge-sort]_). + +Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and [Bellman-Ford Algorithm][bellman-ford] lend themselves better to iteration. + + +```python +from operator import add, mul, sub +from operator import floordiv as div + +# Define a lookup table for mathematical operations +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + + +def answer(question): + # Call clean() and feed it to calculate() + return calculate(clean(question)) + +def clean(question): + # It's not a question unless it starts with 'What is'. + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + # Remove the unnecessary parts of the question and + # parse the cleaned question into a list of items to process. + # The wrapping () invoke implicit concatenation for the chained functions + return (question.removeprefix("What is") + .removesuffix("?") + .replace("by", "") + .strip()).split() # <-- this split() turns the string into a list. + +# Recursively calculate the first piece of the equation, calling +# calculate() on the product + the remainder. +# Return the solution when len(equation) is one. +def calculate(equation): + if len(equation) == 1: + return int(equation[0]) + else: + try: + # Unpack the equation into first int, operator, and second int. + # Stuff the remainder into *rest + x_value, operation, y_value, *rest = equation + + # Redefine the equation list as the product of the first three + # variables concatenated with the unpacked remainder. + equation = [OPERATIONS[operation](int(x_value), + int(y_value)), *rest] + except: + raise ValueError("syntax error") + + # Call calculate() with the redefined/partially reduced equation. + return calculate(equation) +``` + +This approach separates the solution into three functions: + +1. `answer()`, which takes the question and calls `calculate(clean())`, returning the answer to the question. +2. `clean()`, which takes a question string and returns a `list` of parsed words and numbers to calculate from. +3. `calculate()`, which performs the calculations on the `list` recursively, until a single number (_the base case check_) is returned as the answer — or an error is thrown. + +The cleaning logic is separate from the processing logic so that the cleaning steps aren't repeated over and over with each recursive `calculate()` call. +This separation also makes it easier to make changes in processing or calculating without creating conflict or confusion. + +Note that `calculate()` performs the same steps as the `while-loop` from [Import Callables from the Operator Module][approach-import-callables-from-operator] and others. +The difference being that the `while-loop` test for `len()` 1 now occurs as an `if` condition in the function (_the base case_), and the "looping" is now a call to `calculate()` in the `else` condition. +`calculate()` can also use many of the strategies detailed in other approaches, as long as they work with the recursion. + + +`clean()` can also use any of the strategies detailed in other approaches, two of which are below: + +```python + # Alternative 1 to the chained calls is to use a list-comprehension: + return [item for item in + question.strip("?").split() + if item not in ("What", "is", "by")] #<-- The [] of the comprehension invokes implicit concatenation. + + + # Alternative 2 is the built-in filter(), but it can be somewhat hard to read. + return list(filter(lambda x: + x not in ("What", "is", "by"), + question.strip("?").split())) #<-- The () in list() also invokes implicit concatenation. +``` + + +## Variation 1: Use Regex for matching, cleaning, and calculating + + +```python + +import re +from operator import add, mul, sub +from operator import floordiv as div + +# This regex looks for any number 0-9 that may or may not have a - in front of it. +DIGITS = re.compile(r"-?\d+") + +# These regex look for a number (x or y) before and after a phrase or word. +OPERATORS = { + mul: re.compile(r"(?P-?\d+) multiplied by (?P-?\d+)"), + div: re.compile(r"(?P-?\d+) divided by (?P-?\d+)"), + add: re.compile(r"(?P-?\d+) plus (?P-?\d+)"), + sub: re.compile(r"(?P-?\d+) minus (?P-?\d+)"), + } + +# This regex looks for any digit 0-9 (optionally negative) followed by any valid operation, +# ending in any digit (optionally negative). +VALIDATE = re.compile(r"(?P-?\d+) (multiplied by|divided by|plus|minus) (?P-?\d+)") + + +def answer(question): + if (not question.startswith( "What is") or "cubed" in question): + raise ValueError("unknown operation") + + question = question.removeprefix( "What is").removesuffix("?").strip() + + # If after cleaning, there is only one number, return it as an int(). + if DIGITS.fullmatch(question): + return int(question) + + # If after cleaning, there isn't anything, toss an error. + if not question: + raise ValueError("syntax error") + + # Call the recursive calculate() function. + return calculate(question) + +# Recursively calculate the first piece of the equation, calling +# calculate() on the product + the remainder. +# Return the solution when len(equation) is one. +def calculate(question): + new_question = "" + + for symbol, pattern in OPERATORS.items(): + # Declare match variable and assign the pattern match as a value + if match := pattern.match(question): + + # Attempt to calculate the first num symbol num trio. + # Convert strings to ints where needed. + first_calc = f"{symbol(int(match['x']), int(match['y']))}" + + # Strip the pattern from the question + remainder = question.removeprefix(match.group()) + + # Create new question with first calculation + the remainder + new_question = first_calc + remainder + + # Check if there is just a single number, so that it can be returned. + # This is the "base case" of this recursive function. + if DIGITS.fullmatch(new_question): + return int(new_question) + + # Check if the new question is still a "valid" question. + # Error out if not. + elif not VALIDATE.match(new_question): + raise ValueError("syntax error") + + # Otherwise, call yourself to process the new question. + else: + return calculate(new_question) +``` + + +This variation shows how the dictionary of operators from `operator` can be augmented with [regex][re] to perform string matching for a question. +Regex are also used here to check that a question is a valid and to ensure that the base case (_nothing but digits are left in the question_) is met for the recursive call in `calculate()`. +The regex patterns use [named groups][named-groups] for easy reference, but it's not necessary to do so. + + +Interestingly, `calculate()` loops through `dict.items()` to find symbols, using a [walrus operator][walrus] to complete successive regex matches and composing an [f-string][f-string] to perform the calculation. +The question remains a `str` throughout the process, so `question.removeprefix(match.group())` is used to "reduce" the original question to form a remainder that is then concatenated with the `f-string` to form a new question. + + +Because each new iteration of the question needs to be validated, there is an `if-elif-else` block at the end that returns the answer, throws a `ValueError("syntax error")`, or makes the recursive call. + + +Note that the `for-loop` and VALIDATE use [`re.match`][re-match], but DIGITS validation uses [`re.fullmatch`][re-fullmatch]. + + +## Variation 2: Use Regex, Recurse within the For-loop + + +```python +import re +from operator import add, mul, sub +from operator import floordiv as div + +DIGITS = re.compile(r"-?\d+") +OPERATORS = ( + (mul, re.compile(r"(?P.*) multiplied by (?P.*)")), + (div, re.compile(r"(?P.*) divided by (?P.*)")), + (add, re.compile(r"(?P.*) plus (?P.*)")), + (sub, re.compile(r"(?P.*) minus (?P.*)")), + ) + +def answer(question): + if not question.startswith( "What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix( "What is").removesuffix("?").strip() + + if not question: + raise ValueError("syntax error") + + return calculate(question) + +def calculate(question): + if DIGITS.fullmatch(question): + return int(question) + + for operation, pattern in OPERATORS: + if match := pattern.match(question): + return operation(calculate(match['x']), calculate(match['y'])) #<-- the loop is paused here to make the two recursive calls. + raise ValueError("syntax error") +``` + +This solution uses a `tuple` of nested `tuples` containing the operators from `operator` and regex in place of the dictionaries that have been used in the previous approaches. +This saves some space, but requires that the nested `tuples` be unpacked as the main `tuple` is iterated over (_note the `for operation, pattern in OPERATORS:` in the `for-loop`_ ) so that operations can be matched to strings in the question. + The regex is also more generic than the example above (_anything before and after the operation words is allowed_). + +Recursion is used a bit differently here from the previous variations — the calls are placed [within the `for-loop`][recursion-within-loops]. +Because the regex are more generic, they will match a `digit-operation-digit` trio in a longer question, so the line `return operation(calculate(match['x']), calculate(match['y']))` is effectively splitting a question into parts that can then be worked on in their own stack frames. + +For example: + +1. "1 plus -10 multiplied by 13 divided by 2" would match on "1 plus -10" (_group x_) **multiplied by** "13 divided by 2" (_group y_). +2. This would then be re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` +3. At this point the loop would pause as the two recursive calls to `calculate()` spawn +4. The loops would run again — and so would the calls to `calculate()`, until there wasn't any match that caused a split of the question or an error. +5. One at a time, the numbers would then be returned, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` could be solved, at which point the answer would be returned. + +For a more visual picture, you can step through the code on [pythontutor.com][recursion-in-loop-pythontutor]. + +[amortization]: https://www.investopedia.com/terms/a/amortization.asp +[approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator +[backtracking]: https://en.wikipedia.org/wiki/Backtracking +[bellman-ford]: https://www.programiz.com/dsa/bellman-ford-algorithm +[bfs]: https://en.wikipedia.org/wiki/Breadth-first_search +[bisection-search]: https://en.wikipedia.org/wiki/Bisection_method +[depreciation]: https://www.investopedia.com/terms/d/depreciation.asp +[dfs]: https://en.wikipedia.org/wiki/Depth-first_search +[dijkstra]: https://www.programiz.com/dsa/dijkstra-algorithm +[dynamic-programming]: https://algo.monster/problems/dynamic_programming_intro +[f-string]: https://docs.python.org/3.11/reference/lexical_analysis.html#formatted-string-literals +[looping-vs-recursion]: https://softwareengineering.stackexchange.com/questions/303242/is-there-anything-that-can-be-done-with-recursion-that-cant-be-done-with-loops +[memoization]: https://inventwithpython.com/recursion/chapter7.html +[merge-sort]: https://www.digitalocean.com/community/tutorials/merge-sort-algorithm-java-c-python +[named-groups]: https://docs.python.org/3/howto/regex.html#non-capturing-and-named-groups +[nuclei-decay]: https://courses.lumenlearning.com/suny-physics/chapter/31-5-half-life-and-activity/ +[re-fullmatch]: https://docs.python.org/3/library/re.html#re.full-match +[re-match]: https://docs.python.org/3/library/re.html#re.match +[re]: https://docs.python.org/3/library/re.html +[recursion-and-iteration]: https://web.mit.edu/6.102/www/sp23/classes/11-recursive-data-types/recursion-and-iteration-review.html#:~:text=The%20converse%20is%20also%20true,we%20are%20trying%20to%20solve. +[recursion-in-loop-pythontutor]: https://pythontutor.com/render.html#code=import%20re%0Afrom%20operator%20import%20add,%20mul,%20sub%0Afrom%20operator%20import%20floordiv%20as%20div%0A%0ADIGITS%20%3D%20re.compile%28r%22-%3F%5Cd%2B%22%29%0AOPERATORS%20%3D%20%28%0A%20%20%20%20%28mul,%20re.compile%28r%22%28%3FP%3Cx%3E.*%29%20multiplied%20by%20%28%3FP%3Cy%3E.*%29%22%29%29,%0A%20%20%20%20%28div,%20re.compile%28r%22%28%3FP%3Cx%3E.*%29%20divided%20by%20%28%3FP%3Cy%3E.*%29%22%29%29,%0A%20%20%20%20%28add,%20re.compile%28r%22%28%3FP%3Cx%3E.*%29%20plus%20%28%3FP%3Cy%3E.*%29%22%29%29,%0A%20%20%20%20%28sub,%20re.compile%28r%22%28%3FP%3Cx%3E.*%29%20minus%20%28%3FP%3Cy%3E.*%29%22%29%29,%0A%20%20%20%20%29%0A%0Adef%20answer%28question%29%3A%0A%20%20%20%20if%20not%20question.startswith%28%20%22What%20is%22%29%20or%20%22cubed%22%20in%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22unknown%20operation%22%29%0A%20%20%20%20%0A%20%20%20%20question%20%3D%20question.removeprefix%28%20%22What%20is%22%29.removesuffix%28%22%3F%22%29.strip%28%29%0A%0A%20%20%20%20if%20not%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%20%20%20%20%0A%20%20%20%20return%20calculate%28question%29%0A%0Adef%20calculate%28question%29%3A%0A%20%20%20%20if%20DIGITS.fullmatch%28question%29%3A%0A%20%20%20%20%20%20%20%20return%20int%28question%29%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20for%20operation,%20pattern%20in%20OPERATORS%3A%0A%20%20%20%20%20%20%20%20if%20match%20%3A%3D%20pattern.match%28question%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20operation%28calculate%28match%5B'x'%5D%29,%20calculate%28match%5B'y'%5D%29%29%20%23%3C--%20the%20loop%20is%20paused%20here%20to%20make%20the%20two%20recursive%20calls.%0A%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%0Aprint%28answer%28%22What%20is%201%20plus%20-10%20multiplied%20by%2013%20divided%20by%202%3F%22%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false +[recursion-is-not-a-superpower]: https://inventwithpython.com/blog/2021/09/05/recursion-is-not-a-superpower-an-iterative-ackermann/ +[recursion-within-loops]: https://stackoverflow.com/questions/4795527/how-recursion-works-inside-a-for-loop +[tail-call-optimization]: https://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +[walrus]: https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-assignment_expression diff --git a/exercises/practice/wordy/.approaches/recursion/snippet.txt b/exercises/practice/wordy/.approaches/recursion/snippet.txt new file mode 100644 index 0000000000..373481f8f4 --- /dev/null +++ b/exercises/practice/wordy/.approaches/recursion/snippet.txt @@ -0,0 +1,8 @@ +def calculate(equation): + if len(equation) == 1: return int(equation[0]) + else: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), *rest] + except: raise ValueError("syntax error") + return calculate(equation) \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md b/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md new file mode 100644 index 0000000000..d3d5c21430 --- /dev/null +++ b/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md @@ -0,0 +1,98 @@ +# Regex and the Operator Module + + +```python +import re +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied by": mul, "divided by": div} + +REGEX = { + 'number': re.compile(r'-?\d+'), + 'operator': re.compile(f'(?:{"|".join(OPERATIONS)})\\b') +} + +# Helper function to extract a number from the question. +def get_number(question): + # Match a number. + pattern = REGEX['number'].match(question) + + # Toss an error if there is no match. + if not pattern: + raise ValueError("syntax error") + + # Remove the matched pattern from the question, and convert + # that same pattern to an int. Return the modified question and the int. + return [question.removeprefix(pattern.group(0)).lstrip(), + int(pattern.group(0))] + +# Helper function to extract an operation from the question. +def get_operation(question): + # Match an operation word + pattern = REGEX['operator'].match(question) + + # Toss an error if there is no match. + if not pattern: + raise ValueError("unknown operation") + + # Remove the matched pattern from the question, and look up + # that same pattern in OPERATIONS. Return the modified question and the operator. + return [question.removeprefix(pattern.group(0)).lstrip(), + OPERATIONS[pattern.group(0)]] + +def answer(question): + prefix = "What is" + + # Toss an error right away if the question isn't valid. + if not question.startswith(prefix): + raise ValueError("unknown operation") + + # Clean the question by removing the suffix and prefix and whitespace. + question = question.removesuffix("?").removeprefix(prefix).lstrip() + + # the question should start with a number + question, result = get_number(question) + + # While there are portions of the question left, continue to process. + while len(question) > 0: + # can't have a number followed by a number + if REGEX['number'].match(question): + raise ValueError("syntax error") + + # Call get_operation and unpack the result + # into question and operation. + question, operation = get_operation(question) + + # Call get_number and unpack the result + # into question and num + question, num = get_number(question) + + # Perform the calculation, using result and num as + # arguments to operation. + result = operation(result, num) + + return result +``` + +This approach uses two dictionaries: one of operations imported from `operators`, and another that holds regex for matching digits and matching operations in the text of a question. + +It defines two "helper" functions, `get_number()` and `get_operation`, that take a question and use the regex patterns to remove, convert, and return a number (_`get_number()`_) or an operation (_`get_operation()`_), along with a modified "new question". + +In the `answer()` function, the question is checked for validity (_does it start with "What is"_), and a `ValueError("unknown operation")` it raised if it is not a valid question. +Next, the question is cleaned with [`str.removeprefix`][removeprefix] & [`str.removesuffix`][removesuffix], removing "What is" and "?". +Left-trailing white space is stripped with the help of [`lstrip()`][lstrip]. +After that, the variable `result` is declared with an initial value from `get_number()`. + +The question is then iterated over via a `while-loop`, which calls `get_operation()` and `get_number()` — "reducing" the question by removing the leading numbers and operator. +The return values from each call are [unpacked][unpacking] into a "leftover" question portion, and the number or operator. +The returned operation is then made [callable][callable] using `()`, with result and the "new" number (_returned from `get_number()`_) passed as arguments. +The `loop` then proceeds with processing of the "new question", until the `len()` is 0. + +Once there is no more question to process, `result` is returned as the answer. + +[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ +[lstrip]: https://docs.python.org/3/library/stdtypes.html#str.lstrip +[removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix +[unpacking]: https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/ diff --git a/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt b/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt new file mode 100644 index 0000000000..4d89edb537 --- /dev/null +++ b/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt @@ -0,0 +1,8 @@ +while len(question) > 0: + if REGEX['number'].match(question): + raise ValueError("syntax error") + + question, operation = get_operation(question) + question, num = get_number(question) + + result = operation(result, num) \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md new file mode 100644 index 0000000000..3c3dcfe8ee --- /dev/null +++ b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md @@ -0,0 +1,161 @@ +# String, List, and Dictionary Methods + + +```python +OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'} + + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if not question: + raise ValueError("syntax error") + + formula = [] + for operation in question.split(): + if operation == 'by': + continue + else: + formula.append(OPERATIONS.get(operation, operation)) + + while len(formula) > 1: + try: + x_value, y_value = int(formula[0]), int(formula[2]) + symbol = formula[1] + remainder = formula[3:] + + if symbol == "+": + formula = [x_value + y_value] + remainder + elif symbol == "-": + formula = [x_value - y_value] + remainder + elif symbol == "*": + formula = [x_value * y_value] + remainder + elif symbol == "/": + formula = [x_value / y_value] + remainder + else: + raise ValueError("syntax error") + except: + raise ValueError("syntax error") + + return int(formula[0]) +``` + +Within the `answer()` function, the question is first checked for "unknown operations" by validating that it starts with "What is" ([`str.startswith`][startswith], [`str.endswith`][endswith]) and does not include the word "cubed" (_which is an invalid operation_). +This eliminates all the [current cases][unknown-operation-tests] where a [`ValueError("unknown operation")`][value-error] needs to be [raised][raise-statement]. +Should the definition of a question expand or change, this strategy would need to be revised. + + +The question is then "cleaned" by removing the prefix "What is" and the suffix "?" ([`str.removeprefix`][removeprefix], [`str.removesuffix`][removesuffix]) and [stripping][strip] any leading or trailing whitespaces. + + +If the question is now an empty string, a `ValueError("syntax error")` is raised. + + +Next, the question is [split][split] into a `list` and iterated over, with each element looked up and replaced from the OPERATIONS dictionary. +The [`dict.get`][dict-get] method is used for this, as it takes a default argument for when a [`KeyError`][keyerror] is thrown. +Here the default for `dict.get` is set to the element being iterated over, which is effectively _"if not found, skip it"_. + This avoids error handling, extra logic, or interruption when an element is not found. + One exception here is the word "by", which is explicitly skipped within the `for-loop`, so that it doesn't appear in the formula to be processed. + This filtering out could also be accomplished by using [`str.replace`][str-replace] in the cleaning step or during the `split` step. + The results of iterating through the question are then appended to a new formula `list`. + + + +````exercism/note +There are a couple of common alternatives to the `loop-append`: + +1. [`list-comprehensions`][list-comprehension] duplicate the same process in a more succinct and declarative fashion: + ```python + + formula = [OPERATIONS.get(operation, operation) for + operation in question.split() if operation != 'by'] + ``` + +2. The built-in [`filter()`][filter] and [`map()`][map] functions used with a [`lambda`][lambdas] to process the elements of the list. + This is identical in process to both the `loop-append` and the `list-comprehension`, but might be easier to reason about for those coming from a more functional programming language: + + ```python + formula = list(map(lambda x : OPERATIONS.get(x, x), + filter(lambda x: x != "by", question.split()))) + ``` + +[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[lambdas]: https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression +[filter]: https://docs.python.org/3/library/functions.html#filter +[map]: https://docs.python.org/3/library/functions.html#map +```` + + +After the formula `list` is composed, it is processed in a `while-loop`. + +The processing within the `loop` is wrapped in a [try-except][handling-exceptions] block to trap any errors and raise them as `ValueError("syntax error")`. +While each type of error could be checked for individually, it is not necessary since only `ValueError("syntax error")` is required here. + +1. `x_value` and `y_value` are assigned to the first element and third element of the list using [bracket notation][bracket-notation], and converted to integers. + - Rather than indexing and slicing, [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment) can be used to assign the variables. + This does require a modification to the returned formula `list`: + ```python + x_value, operation, y_value, *remainder = formula # <-- Unpacking won't allow conversion to int() here. + + ... + if symbol == "+": + formula = [int(x_value) + int(y_value)] + remainder # <-- Instead, conversion to int() must happen here. + ... + + return int(formula[0]) + ``` + +2. `symbol` is assigned to the second element of the list. +3. `remainder` is assigned to a [slice][list-slice] of everything else in the `list`. + +The `symbol` is then tested in the `if-elif-else` block and the formula `list` is modified by calculating the operation on `x_value` and `y_value` and then appending whatever part of the question remains. + +Once the formula `list` is calculated down to a number, that number is converted to an `int` and returned as the answer. + + +````exercism/note +Introduced in Python 3.10, [structural pattern matching][structural-pattern-matching] can be used to replace the `if-elif-else` chain in the `while-loop`. +In some circumstances, this could be easier to read and/or reason about: + + +```python + while len(formula) > 1: + try: + x_value, symbol, y_value, *remainder = formula + + match symbol: + case "+": + formula = [int(x_value) + int(y_value)] + remainder + case "-": + formula = [int(x_value) - int(y_value)] + remainder + case "*": + formula = [int(x_value) * int(y_value)] + remainder + case "/": + formula = [int(x_value) / int(y_value)] + remainder + case _: + raise ValueError("syntax error") + except: + raise ValueError("syntax error") +``` + +[structural-pattern-matching]: https://peps.python.org/pep-0636/ +```` + +[bracket-notation]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get +[endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[keyerror]: https://docs.python.org/3/library/exceptions.html#KeyError +[list-slice]: https://www.pythonmorsels.com/slicing/ +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix +[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split +[startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith +[str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace +[strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[unknown-operation-tests]: https://github.com/exercism/python/blob/main/exercises/practice/wordy/wordy_test.py#L58-L68 +[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError diff --git a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt new file mode 100644 index 0000000000..700804b6d1 --- /dev/null +++ b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt @@ -0,0 +1,8 @@ +try: + x_value, y_value, symbol, remainder = int(formula[0]), int(formula[2]), formula[1], formula[3:] + if symbol == "+": formula = [x_value + y_value] + remainder + elif symbol == "-": formula = [x_value - y_value] + remainder + elif symbol == "*": formula = [x_value * y_value] + remainder + elif symbol == "/": formula = [x_value / y_value] + remainder + else: raise ValueError("syntax error") +except: raise ValueError("syntax error") \ No newline at end of file diff --git a/exercises/practice/wordy/.docs/instructions.append.md b/exercises/practice/wordy/.docs/instructions.append.md index 0a0d309f7e..ca646b7bd9 100644 --- a/exercises/practice/wordy/.docs/instructions.append.md +++ b/exercises/practice/wordy/.docs/instructions.append.md @@ -2,11 +2,12 @@ ## Exception messages -Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. +Sometimes it is necessary to [raise an exception][raise-an-exception]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][built-in-errors], but should still include a meaningful message. -This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the question passed to `answer()` is malformed/invalid, or contains an unknown operation. The tests will only pass if you both `raise` the `exception` and include a message with it. +This particular exercise requires that you use the [raise statement][raise-statement] to "throw" a `ValueError` if the question passed to `answer()` is malformed/invalid, or contains an unknown operation. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a [`ValueError`][value-error] with a message, write the message as an argument to the `exception` type: -To raise a `ValueError` with a message, write the message as an argument to the `exception` type: ```python # if the question contains an unknown operation. @@ -15,3 +16,22 @@ raise ValueError("unknown operation") # if the question is malformed or invalid. raise ValueError("syntax error") ``` + +To _handle_ a raised error within a particular code block, one can use a [try-except][handling-exceptions] : + +```python +while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + + ... + + except: + raise ValueError("syntax error") +``` + +[built-in-errors]: https://docs.python.org/3.11/library/exceptions.html#built-in-exceptions +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[raise-an-exception]: https://docs.python.org/3/tutorial/errors.html#raising-exceptions +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError From dd39c7176da4645c2d1ecd965ebf638f32473d01 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 11 Oct 2024 18:25:46 -0700 Subject: [PATCH 720/826] [Wordy]: Link Corrections and Typos (#3784) * Added 6 additional appraches and extended introduction for Wordy. * Corrected slug for regex with operator module approach. * Fixed link typos for other approaches and the filter function. * One. Moar. Typo. --- .../wordy/.approaches/functools-reduce/content.md | 3 ++- .../import-callables-from-operator/content.md | 2 +- exercises/practice/wordy/.approaches/introduction.md | 9 +++++---- .../wordy/.approaches/lambdas-in-a-dictionary/content.md | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md index 08f21a8ad2..0a2532d442 100644 --- a/exercises/practice/wordy/.approaches/functools-reduce/content.md +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -119,7 +119,8 @@ def answer(question): return result ``` -[approach-lambdas-in-a-dictionary]: https://exercise.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary + +[approach-lambdas-in-a-dictionary]: https://exercism.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary [callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce [method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md index 0c2683760a..9f57c1c9c6 100644 --- a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md +++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md @@ -76,7 +76,7 @@ equation = question.replace("by", "").split() ``` [aliasing]: https://mimo.org/glossary/python -[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods [callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ [chaining-method-calls]: https://nikhilakki.in/understanding-method-chaining-in-python [handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index ae8900e08f..0ea7592704 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -409,10 +409,10 @@ For more detail on this solution, take a look at the [dunder method with `__geta [approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute [approach-functools-reduce]: https://exercism.org/tracks/python/exercises/wordy/approaches/functools-reduce [approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator -[approach-lambdas-in-a-dictionary]: https://exercise.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary -[approach-recursion]: https://exercise.org/tracks/python/exercises/wordy/approaches/recursion -[approach-regex-with-operator-module]: https://exercise.org/tracks/python/exercises/wordy/approaches/regex-with-operator-module -[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[approach-lambdas-in-a-dictionary]: https://exercsim.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary +[approach-recursion]: https://exercism.org/tracks/python/exercises/wordy/approaches/recursion +[approach-regex-with-operator-module]: https://exercism.org/tracks/python/exercises/wordy/approaches/regex-with-operator-module +[approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods [callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ [dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get [dict]: https://docs.python.org/3/library/stdtypes.html#dict @@ -421,6 +421,7 @@ For more detail on this solution, take a look at the [dunder method with `__geta [eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu [eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html [eval]: https://docs.python.org/3/library/functions.html?#eval +[filter]: https://docs.python.org/3/library/functions.html#filter [find]: https://docs.python.org/3.9/library/stdtypes.html#str.find [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce [getattribute]: https://docs.python.org/3/reference/datamodel.html#object.__getattribute__ diff --git a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md index 6836e7b468..df06d3b171 100644 --- a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md +++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md @@ -91,7 +91,7 @@ In addition, the functions need to carry a trailing underscore to avoid potentia It is better and cleaner in this circumstance to use `lambda expressions` for the functions - although it could be argued that importing and using the methods from `operator` is even better and clearer. [approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator -[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods [filter]: https://docs.python.org/3/library/functions.html#filter [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce [key-functions]: https://docs.python.org/3/howto/sorting.html#key-functions From 6800ddb6a2faf1dadcf66213e53b6ef41c372ace Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 11 Oct 2024 21:14:00 -0700 Subject: [PATCH 721/826] Fixed even more typos in recursion approach. (#3785) --- exercises/practice/wordy/.approaches/config.json | 2 +- .../practice/wordy/.approaches/recursion/content.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/practice/wordy/.approaches/config.json b/exercises/practice/wordy/.approaches/config.json index 79a3a114ba..670284d471 100644 --- a/exercises/practice/wordy/.approaches/config.json +++ b/exercises/practice/wordy/.approaches/config.json @@ -35,7 +35,7 @@ { "uuid": "2e643b88-9b76-45a1-98f4-b211919af061", "slug": "recursion", - "title": "Recursion for iteration.", + "title": "Recursion for Iteration.", "blurb": "Use recursion with other strategies to solve word problems.", "authors": ["BethanyG"] }, diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md index 5dd0a7ba37..8131cbb7c9 100644 --- a/exercises/practice/wordy/.approaches/recursion/content.md +++ b/exercises/practice/wordy/.approaches/recursion/content.md @@ -1,17 +1,17 @@ -# Recursion +# Recursion for Iteration [Any function that can be written iteratively (_with loops_) can be written using recursion][recursion-and-iteration], and [vice-versa][recursion-is-not-a-superpower]. A recursive strategy [may not always be obvious][looping-vs-recursion] or easy — but it is always possible. So the `while-loop`s used in other approaches to Wordy can be re-written to use recursive calls. -That being said, Pyton famously does not perform [tail-call optimization][tail-call-optimization], and limits recursive calls on the stack to a depth of 1000 frames, so it is important to only use recursion where you are confident that it can complete within the limit (_or something close to it_). +That being said, Python famously does not perform [tail-call optimization][tail-call-optimization], and limits recursive calls on the stack to a depth of 1000 frames, so it is important to only use recursion where you are confident that it can complete within the limit (_or something close to it_). [Memoization][memoization] and other strategies in [dynamic programming][dynamic-programming] can help to make recursion more efficient and "shorter" in Python, but it's always good to give it careful consideration. Recursion works best with problem spaces that resemble trees, include [backtracking][backtracking], or become progressively smaller. - Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-firs search][dfs], and [merge sort][merge-sort]_). + Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-firs search][dfs], and [merge sort][merge-sort]. -Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and [Bellman-Ford Algorithm][bellman-ford] lend themselves better to iteration. +Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and the [Bellman-Ford Algorithm][bellman-ford] lend themselves better to iteration. ```python @@ -170,7 +170,7 @@ def calculate(question): ``` -This variation shows how the dictionary of operators from `operator` can be augmented with [regex][re] to perform string matching for a question. +This variation shows how the dictionary of operators from `operator` can be augmented with [regex][re] to perform string matching for a question. Regex are also used here to check that a question is a valid and to ensure that the base case (_nothing but digits are left in the question_) is met for the recursive call in `calculate()`. The regex patterns use [named groups][named-groups] for easy reference, but it's not necessary to do so. From d2cf34b90a1efe5af5c27cdc30c7ff26050be4b6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 12 Oct 2024 11:35:12 -0700 Subject: [PATCH 722/826] Changed Wordy difficulty to 3 and reorderd config.json. (#3786) --- config.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/config.json b/config.json index 38dd7520e3..09bd642c98 100644 --- a/config.json +++ b/config.json @@ -363,21 +363,6 @@ ], "difficulty": 1 }, - { - "slug": "wordy", - "name": "Wordy", - "uuid": "af50bb9a-e400-49ce-966f-016c31720be1", - "practices": ["string-methods"], - "prerequisites": [ - "basics", - "lists", - "loops", - "strings", - "string-methods", - "numbers" - ], - "difficulty": 1 - }, { "slug": "resistor-color", "name": "Resistor Color", @@ -1210,6 +1195,21 @@ ], "difficulty": 3 }, + { + "slug": "wordy", + "name": "Wordy", + "uuid": "af50bb9a-e400-49ce-966f-016c31720be1", + "practices": ["string-methods"], + "prerequisites": [ + "basics", + "lists", + "loops", + "strings", + "string-methods", + "numbers" + ], + "difficulty": 3 + }, { "slug": "crypto-square", "name": "Crypto Square", From 63cc10b06f425edb3a85c404d15d360c333bde0b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 14 Oct 2024 16:29:08 -0700 Subject: [PATCH 723/826] Further refinements to approaches. Added hits.md file. (#3788) --- .../.approaches/functools-reduce/content.md | 2 +- .../wordy/.approaches/introduction.md | 89 +++++---- .../string-list-and-dict-methods/content.md | 183 ++++++++++++------ .../wordy/.docs/instructions.append.md | 16 +- 4 files changed, 191 insertions(+), 99 deletions(-) diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md index 0a2532d442..602a678ffd 100644 --- a/exercises/practice/wordy/.approaches/functools-reduce/content.md +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -33,7 +33,7 @@ def answer(question): raise ValueError("syntax error") # Evaluate the expression from left to right using functools.reduce(). - # Look up each operation in the operation dictionary. + # Look up each operation in the OPERATORS dictionary. return reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) ``` diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 0ea7592704..6f23f040ba 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -8,17 +8,39 @@ This means that for some of the test cases, the solution will not be the same as ## General Guidance The key to a Wordy solution is to remove the "question" portion of the sentence (_"What is", "?"_) and process the remaining words between numbers as [operators][mathematical operators]. -If a single number remains after removing the "question", it should be converted to an [`int`][int] and returned as the answer. + + +If a single number remains after removing the "question" pieces, it should be converted to an [`int`][int] and returned as the answer. + + Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a [`ValueError`][value-error] with a message. This includes any "extra" spaces between numbers. + One way to reduce the number of `raise` statements/ `ValueError`s needed in the code is to determine if a problem is a "valid" question _before_ proceeding to parsing and calculation. As shown in various approaches, there are multiple strategies for validating questions, with no one "canonical" solution. -One very effective approach is to check if a question starts with "What is", ends with "?", and includes only valid operations. -That could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. -There are various Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. -For cleaning the "question" portion of the problem, [`str.removeprefix`][removeprefix] and + +One very effective validation approach is to check if a question starts with "What is", ends with "?", and does not include the word "cubed". +Any other question formulation becomes a `ValueError("unknown operation")`. +This very restrictive approach could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. + + +Proceeding from validation, there are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. +However, they all follow these general steps: + +1. Remove the parts of the question string that do not apply to calculating the answer. +2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations. + - _Converting the question string into a `list` of words is hugely helpful here, but not absolutely necessary._ +3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to +, -, *, /. +4. Apply the operation to the numbers, which should result in a single number. + - _Employing a `try-except` block around the conversion and operator application steps can trap any errors thrown and make the code both "safer" and less complex._ +5. Use the calculated number from step 4 as the start for the next "trio" (_number, operation, number_) in the question. The calculated number + the remainder of the question becomes the question being worked on in the next iteration. + - _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ +6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`. + + +For cleaning the question, [`str.removeprefix`][removeprefix] and [`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful: @@ -53,73 +75,70 @@ You can also use [`str.startswith`][startswith] and [`str.endswith`][endswith] i ``` -Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.index`][index] with string slicing could be used to clean up the initial word problem. -A [regex][regex] could also be used to process the question, but might be considered overkill given the fixed nature of the prefix/suffix and operations. +Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.index`][index] with string slicing could also be used to clean up the initial question. +A [regex][regex] could be used to process the question as well, but might be considered overkill given the fixed nature of the prefix/suffix and operations. Finally, [`str.strip`][strip] and its variants are very useful for cleaning up any leftover leading or trailing whitespace. -Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient iteration, although other strategies are also used. +Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient looping/iteration, although other strategies can also be used. + For math operations, many solutions involve importing and using methods from the [operator][operator] module in combination with different looping, parsing, and substitution strategies. -Some solutions use either [lambda][lambdas] expressions or [dunder/"special" methods][dunder-methods] to replace words with arithmetic operations. -However, the exercise can be solved without using `operator`, `lambdas`, or `dunder-methods`. +Some solutions use either [lambda][lambdas] expressions, [dunder/"special" methods][dunder-methods], or even `eval()` to replace words with arithmetic operations. +However, the exercise can be solved **without** using `operator`, `lambdas`, `dunder-methods` or `eval`. + It is recommended that you first start by solving it _without_ "advanced" strategies, and then refine your solution into something more compact or complex as you learn and practice. + +~~~~exercism/caution Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach. It is also entirely unnecessary, as the other methods described here are safer and equally performant. +~~~~ ## Approach: String, List, and Dictionary Methods ```python -OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'} - - def answer(question): if not question.startswith("What is") or "cubed" in question: raise ValueError("unknown operation") - question = question.removeprefix("What is").removesuffix("?").strip() + question = question.removeprefix("What is") + question = question.removesuffix("?") + question = question.replace("by", "") + question = question.strip() if not question: raise ValueError("syntax error") - - if question.isdigit(): - return int(question) - - formula = [] - for operation in question.split(): - if operation == 'by': - continue - else: - formula.append(OPERATIONS.get(operation, operation)) + formula = question.split() while len(formula) > 1: try: x_value = int(formula[0]) - symbol = formula[1] y_value = int(formula[2]) + symbol = formula[1] remainder = formula[3:] - if symbol == "+": + if symbol == "plus": formula = [x_value + y_value] + remainder - elif symbol == "-": + elif symbol == "minus": formula = [x_value - y_value] + remainder - elif symbol == "*": + elif symbol == "multiplied": formula = [x_value * y_value] + remainder - elif symbol == "/": + elif symbol == "divided": formula = [x_value / y_value] + remainder else: raise ValueError("syntax error") except: raise ValueError("syntax error") - return formula[0] + return int(formula[0]) ``` -This approach uses only data structures and methods (_[dict][dict], [dict.get()][dict-get] and [list()][list]_) from core Python, and does not import any extra modules. +This approach uses only data structures and methods (_[str methods][str-methods], [list()][list], loops, etc._) from core Python, and does not import any extra modules. It may have more lines of code than average, but it is clear to follow and fairly straightforward to reason about. It does use a [try-except][handling-exceptions] block for handling unknown operators. -As an alternative to the `formula` loop-append, a [list-comprehension][list-comprehension] can be used to create the initial parsed formula. + +Alternatives could use a [dictionary][dict] to store word --> operator mappings that could be looked up in the `while-loop` using [`.get()`][dict-get], among other strategies. For more details and variations, read the [String, List and Dictionary Methods][approach-string-list-and-dict-methods] approach. @@ -350,7 +369,7 @@ def answer(question): ``` -This approach replaces the `while-loop` used in many solutions (_or the `recursion` strategy outlined in the approach above_) with a call to [`functools.reduce`][functools-reduce]. +This approach replaces the `while-loop` used in many solutions (_or the `recursion` strategy outlined in the approach above_) with a call to [`functools.reduce`][functools-reduce]. It also employs a lookup dictionary for methods imported from the `operator` module, as well as a `list-comprehension`, the built-in [`filter`][filter] function, and multiple string [slices][sequence-operations]. If desired, the `operator` imports can be replaced with a dictionary of `lambda` expressions or `dunder-methods`. @@ -418,9 +437,6 @@ For more detail on this solution, take a look at the [dunder method with `__geta [dict]: https://docs.python.org/3/library/stdtypes.html#dict [dunder-methods]: https://www.pythonmorsels.com/what-are-dunder-methods/?watch [endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith -[eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu -[eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html -[eval]: https://docs.python.org/3/library/functions.html?#eval [filter]: https://docs.python.org/3/library/functions.html#filter [find]: https://docs.python.org/3.9/library/stdtypes.html#str.find [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce @@ -444,4 +460,5 @@ For more detail on this solution, take a look at the [dunder method with `__geta [split]: https://docs.python.org/3.9/library/stdtypes.html#str.split [startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith [strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[str-methods]: https://docs.python.org/3/library/stdtypes.html#string-methods [value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError diff --git a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md index 3c3dcfe8ee..cce88a4bb0 100644 --- a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md +++ b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md @@ -1,6 +1,73 @@ # String, List, and Dictionary Methods +```python +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is") + question = question.removesuffix("?") + question = question.replace("by", "") + question = question.strip() + + if not question: + raise ValueError("syntax error") + + formula = question.split() + while len(formula) > 1: + try: + x_value = int(formula[0]) + y_value = int(formula[2]) + symbol = formula[1] + remainder = formula[3:] + + if symbol == "plus": + formula = [x_value + y_value] + remainder + elif symbol == "minus": + formula = [x_value - y_value] + remainder + elif symbol == "multiplied": + formula = [x_value * y_value] + remainder + elif symbol == "divided": + formula = [x_value / y_value] + remainder + else: + raise ValueError("syntax error") + except: + raise ValueError("syntax error") + + return int(formula[0]) +``` + +Within the `answer()` function, the question is first checked for "unknown operations" by validating that it starts with "What is" ([`str.startswith`][startswith], [`str.endswith`][endswith]) and does not include the word "cubed" (_which is an invalid operation_). +This eliminates all the [current cases][unknown-operation-tests] where a [`ValueError("unknown operation")`][value-error] needs to be [raised][raise-statement]. +Should the definition of a question expand or change, this strategy would need to be revised. + + +The question is then "cleaned" by removing the prefix "What is" and the suffix "?" ([`str.removeprefix`][removeprefix], [`str.removesuffix`][removesuffix]), replacing "by" with "" ([`str.replace`][str-replace]), and [stripping][strip] any leading or trailing whitespaces. + + +If the question is now an empty string, a `ValueError("syntax error")` is raised. + + +The remaining question string is then converted into a `list` of elements via [`str.split`][split], and that `list` is iterated over using a `while-loop` with a `len()` > 1 condition. + +Within a [`try-except`][handling-exceptions] block to trap/handle any errors (_which will all map to `ValueError("syntax error")`_), the question `list` is divided up among 4 variables using [bracket notation][bracket-notation]: + +1. The first element, `x_value`. This is assumed to be a number, so it is converted to an `int()` +2. The third element, `y_value`. This is also assumed to be a number and converted to an `int()`. +3. The second element, `symbol`. This is assumed to be an operator, and is left as-is. +4. The `remainder` of the question, if there is any. This is a [slice][list-slice] starting at index 3, and going to the end. + + +`symbol` is then tested for "plus, minus, multiplied, or divided", and the `formula` list is modified by applying the given operation, and creating a new `formula` `list` by concatenating a `list` of the first product with the `remainder` list. +If `symbol` doesn't match any known operators, a `ValueError("syntax error")` is raised. + +Once `len(formula) == 1`, the first element (`formula[0]`) is converted to an `int()` and returned as the answer. + + +## Variation 1: Use a Dictionary for Lookup/Replace + + ```python OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'} @@ -9,21 +76,19 @@ def answer(question): if not question.startswith("What is") or "cubed" in question: raise ValueError("unknown operation") - question = question.removeprefix("What is").removesuffix("?").strip() + question = question.removeprefix("What is").removesuffix("?").replace("by", "").strip() if not question: raise ValueError("syntax error") formula = [] for operation in question.split(): - if operation == 'by': - continue - else: - formula.append(OPERATIONS.get(operation, operation)) + formula.append(OPERATIONS.get(operation, operation)) while len(formula) > 1: try: - x_value, y_value = int(formula[0]), int(formula[2]) + x_value = int(formula[0]) + y_value = int(formula[2]) symbol = formula[1] remainder = formula[3:] @@ -43,31 +108,31 @@ def answer(question): return int(formula[0]) ``` -Within the `answer()` function, the question is first checked for "unknown operations" by validating that it starts with "What is" ([`str.startswith`][startswith], [`str.endswith`][endswith]) and does not include the word "cubed" (_which is an invalid operation_). -This eliminates all the [current cases][unknown-operation-tests] where a [`ValueError("unknown operation")`][value-error] needs to be [raised][raise-statement]. -Should the definition of a question expand or change, this strategy would need to be revised. - -The question is then "cleaned" by removing the prefix "What is" and the suffix "?" ([`str.removeprefix`][removeprefix], [`str.removesuffix`][removesuffix]) and [stripping][strip] any leading or trailing whitespaces. +````exercism/note +[chaining][method-chaining] is used in the clean step for this variation, and is the equivalent of assigning and re-assigning `question` as is done in the initial approach. + This is because `str.startswith`, `str.endswith`, and `str.replace` all return strings, so the output of one can be used as the input to the next. + + [method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining +```` -If the question is now an empty string, a `ValueError("syntax error")` is raised. +This variation creates a dictionary to map operation words to symbols. +It pre-processes the question string into a `formula` list by looking up the operation words and replacing them with the symbols via the [`.get`][dict-get] method, which takes a [default argument][default-argument] for when a [`KeyError`][keyerror] is thrown. +Here the default for `dict.get()` is set to the element being iterated over, which is effectively _"if not found, skip it"_. +This means the number strings will be passed through, even though they would otherwise toss an error. + The results of iterating through the question are appended to `formula` via [`list.append`][list-append]. -Next, the question is [split][split] into a `list` and iterated over, with each element looked up and replaced from the OPERATIONS dictionary. -The [`dict.get`][dict-get] method is used for this, as it takes a default argument for when a [`KeyError`][keyerror] is thrown. -Here the default for `dict.get` is set to the element being iterated over, which is effectively _"if not found, skip it"_. - This avoids error handling, extra logic, or interruption when an element is not found. - One exception here is the word "by", which is explicitly skipped within the `for-loop`, so that it doesn't appear in the formula to be processed. - This filtering out could also be accomplished by using [`str.replace`][str-replace] in the cleaning step or during the `split` step. - The results of iterating through the question are then appended to a new formula `list`. +This dictionary is not necessary, but does potentially make adding/tracking future operations easier, although the `if-elif-else` block in the `while-loop` is equally awkward for maintenance (_see the [import callables from operator][approach-import-callables-from-operator] for a way to replace the block_). +The `while-loop`, `if-elif-else` block, and the `try-except` block are then the same as in the initial approach. ````exercism/note -There are a couple of common alternatives to the `loop-append`: +There are a couple of common alternatives to the `loop-append` used here: -1. [`list-comprehensions`][list-comprehension] duplicate the same process in a more succinct and declarative fashion: +1. [`list-comprehensions`][list-comprehension] duplicate the same process in a more succinct and declarative fashion. This one also includes filtering out "by": ```python formula = [OPERATIONS.get(operation, operation) for @@ -88,67 +153,68 @@ There are a couple of common alternatives to the `loop-append`: [map]: https://docs.python.org/3/library/functions.html#map ```` + Rather than indexing and slicing, [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment) can be used to assign the variables. + However, this does require a modification to the returned formula `list`: -After the formula `list` is composed, it is processed in a `while-loop`. - -The processing within the `loop` is wrapped in a [try-except][handling-exceptions] block to trap any errors and raise them as `ValueError("syntax error")`. -While each type of error could be checked for individually, it is not necessary since only `ValueError("syntax error")` is required here. - -1. `x_value` and `y_value` are assigned to the first element and third element of the list using [bracket notation][bracket-notation], and converted to integers. - - Rather than indexing and slicing, [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment) can be used to assign the variables. - This does require a modification to the returned formula `list`: - ```python - x_value, operation, y_value, *remainder = formula # <-- Unpacking won't allow conversion to int() here. - - ... - if symbol == "+": - formula = [int(x_value) + int(y_value)] + remainder # <-- Instead, conversion to int() must happen here. - ... - - return int(formula[0]) - ``` -2. `symbol` is assigned to the second element of the list. -3. `remainder` is assigned to a [slice][list-slice] of everything else in the `list`. + ```python + x_value, operation, y_value, *remainder = formula # <-- Unpacking won't allow conversion to int() here. + + ... + if symbol == "+": + formula = [int(x_value) + int(y_value)] + remainder # <-- Instead, conversion to int() must happen here. + ... + + return int(formula[0]) + ``` -The `symbol` is then tested in the `if-elif-else` block and the formula `list` is modified by calculating the operation on `x_value` and `y_value` and then appending whatever part of the question remains. -Once the formula `list` is calculated down to a number, that number is converted to an `int` and returned as the answer. +## Variation 2: Structural Pattern Matching to Replace `if-elif-else` -````exercism/note -Introduced in Python 3.10, [structural pattern matching][structural-pattern-matching] can be used to replace the `if-elif-else` chain in the `while-loop`. +Introduced in Python 3.10, [structural pattern matching][structural-pattern-matching] can be used to replace the `if-elif-else` chain in the `while-loop` used in the two approaches above. In some circumstances, this could be easier to read and/or reason about: ```python +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").replace("by", "").strip() + + if not question: + raise ValueError("syntax error") + + formula = question.split() while len(formula) > 1: try: - x_value, symbol, y_value, *remainder = formula + x_value, symbol, y_value, *remainder = formula #<-- unpacking and multiple assignment. - match symbol: - case "+": + match symbol: + case "plus": formula = [int(x_value) + int(y_value)] + remainder - case "-": + case "minus": formula = [int(x_value) - int(y_value)] + remainder - case "*": + case "multiplied": formula = [int(x_value) * int(y_value)] + remainder - case "/": + case "divided": formula = [int(x_value) / int(y_value)] + remainder - case _: - raise ValueError("syntax error") - except: - raise ValueError("syntax error") + case _: + raise ValueError("syntax error") #<-- "fall through case for no match." + except: raise ValueError("syntax error") # <-- error handling for anything else that goes wrong. + + return int(formula[0]) ``` -[structural-pattern-matching]: https://peps.python.org/pep-0636/ -```` - +[approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator [bracket-notation]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[default-argument]: https://docs.python.org/3/tutorial/controlflow.html#default-argument-values [dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get [endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith [handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions [keyerror]: https://docs.python.org/3/library/exceptions.html#KeyError +[list-append]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists [list-slice]: https://www.pythonmorsels.com/slicing/ [raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement [removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix @@ -157,5 +223,6 @@ In some circumstances, this could be easier to read and/or reason about: [startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith [str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace [strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[structural-pattern-matching]: https://peps.python.org/pep-0636/ [unknown-operation-tests]: https://github.com/exercism/python/blob/main/exercises/practice/wordy/wordy_test.py#L58-L68 [value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError diff --git a/exercises/practice/wordy/.docs/instructions.append.md b/exercises/practice/wordy/.docs/instructions.append.md index ca646b7bd9..d26afab5ff 100644 --- a/exercises/practice/wordy/.docs/instructions.append.md +++ b/exercises/practice/wordy/.docs/instructions.append.md @@ -5,6 +5,8 @@ Sometimes it is necessary to [raise an exception][raise-an-exception]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][built-in-errors], but should still include a meaningful message. This particular exercise requires that you use the [raise statement][raise-statement] to "throw" a `ValueError` if the question passed to `answer()` is malformed/invalid, or contains an unknown operation. The tests will only pass if you both `raise` the `exception` and include a message with it. +**Please note**: The message needed is different for each scenario, even though the same _error type_ is being raised. +Check the tests carefully. To raise a [`ValueError`][value-error] with a message, write the message as an argument to the `exception` type: @@ -17,16 +19,22 @@ raise ValueError("unknown operation") raise ValueError("syntax error") ``` -To _handle_ a raised error within a particular code block, one can use a [try-except][handling-exceptions] : +To _handle_ a raised error within a particular code block, one can use a [try-except][handling-exceptions]. + `try-except` blocks "wrap" the code that could potentially cause an error, mapping all the exceptions to one error, multiple errors, or other pieces of code to deal with the problem: + ```python while len(equation) > 1: - try: + try: + # The questionable/error-prone code goes here,in an indented block + # It can contain statements, loops, if-else blocks, or other executable code. x_value, operation, y_value, *rest = equation - ... - + ... + except: + # Code for what to do when an error gets thrown in the code above. + # This could be one error, or more complicated logging, error checking and messaging. raise ValueError("syntax error") ``` From c5922804a889978ec08dcd9d08b2604b68bc3bf6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 14 Oct 2024 17:26:36 -0700 Subject: [PATCH 724/826] [Wordy]: Moar Typos & Missing Links. (#3789) * Moar typos and missing links. Hopefully, this is the last round. * Added in missing hints file. * Further touchup on intro md. --- .../wordy/.approaches/introduction.md | 54 ++++++++++++------- exercises/practice/wordy/.docs/hints.md | 40 ++++++++++++++ 2 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 exercises/practice/wordy/.docs/hints.md diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 6f23f040ba..8af7b35835 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -1,46 +1,45 @@ # Introduction The objective of the Wordy exercise is to parse and evaluate small/simple mathematical word problems, returning the result as an integer. -These problems do not require complex or [PEMDAS][PEMDAS]-based evaluation and are instead processed from left-to-right _in sequence_. +These questions do not require complex or [PEMDAS][PEMDAS]-based evaluation and are instead processed from left-to-right _in sequence_. This means that for some of the test cases, the solution will not be the same as if the word problem was evaluated like a traditional math problem. +
+ ## General Guidance The key to a Wordy solution is to remove the "question" portion of the sentence (_"What is", "?"_) and process the remaining words between numbers as [operators][mathematical operators]. - - If a single number remains after removing the "question" pieces, it should be converted to an [`int`][int] and returned as the answer. -Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a [`ValueError`][value-error] with a message. +Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a ["ValueError('syntax error")`][value-error] with a message. This includes any "extra" spaces between numbers. - - -One way to reduce the number of `raise` statements/ `ValueError`s needed in the code is to determine if a problem is a "valid" question _before_ proceeding to parsing and calculation. As shown in various approaches, there are multiple strategies for validating questions, with no one "canonical" solution. -One very effective validation approach is to check if a question starts with "What is", ends with "?", and does not include the word "cubed". +A whole class of error can be eliminated up front by checking if a question starts with "What is", ends with "?", and does not include the word "cubed". Any other question formulation becomes a `ValueError("unknown operation")`. -This very restrictive approach could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. +This could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. -Proceeding from validation, there are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. -However, they all follow these general steps: +~~~~exercism/note +There are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. +However, the solutions all follow these general steps: 1. Remove the parts of the question string that do not apply to calculating the answer. 2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations. - - _Converting the question string into a `list` of words is hugely helpful here, but not absolutely necessary._ -3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to +, -, *, /. + -- _Converting the question string into a `list` of words is hugely helpful here._ +3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to the mathematical operations +, -, *, and /. 4. Apply the operation to the numbers, which should result in a single number. - - _Employing a `try-except` block around the conversion and operator application steps can trap any errors thrown and make the code both "safer" and less complex._ + -- _Employing a `try-except` block can trap any errors thrown and make the code both "safer" and less complex._ 5. Use the calculated number from step 4 as the start for the next "trio" (_number, operation, number_) in the question. The calculated number + the remainder of the question becomes the question being worked on in the next iteration. - - _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ + -- _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ 6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`. +~~~~ -For cleaning the question, [`str.removeprefix`][removeprefix] and +For question cleaning, [`str.removeprefix`][removeprefix] and [`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful: @@ -79,20 +78,37 @@ Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.ind A [regex][regex] could be used to process the question as well, but might be considered overkill given the fixed nature of the prefix/suffix and operations. Finally, [`str.strip`][strip] and its variants are very useful for cleaning up any leftover leading or trailing whitespace. -Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient looping/iteration, although other strategies can also be used. +Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient looping/iteration, although other strategies can also be used: -For math operations, many solutions involve importing and using methods from the [operator][operator] module in combination with different looping, parsing, and substitution strategies. +```python +>>> sentence = "The quick brown fox jumped over the lazy dog 10 times" +>>> sentence.split() +['The', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog', '10', 'times'] +``` + + +For math operations, many solutions involve importing and using methods from the [operator][operator] module. Some solutions use either [lambda][lambdas] expressions, [dunder/"special" methods][dunder-methods], or even `eval()` to replace words with arithmetic operations. -However, the exercise can be solved **without** using `operator`, `lambdas`, `dunder-methods` or `eval`. + + +However, the exercise can be solved without using `operator`, `lambdas`, `dunder-methods` or `eval`. It is recommended that you first start by solving it _without_ "advanced" strategies, and then refine your solution into something more compact or complex as you learn and practice. ~~~~exercism/caution Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach. It is also entirely unnecessary, as the other methods described here are safer and equally performant. + +[eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu +[eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html +[eval]: https://docs.python.org/3/library/functions.html?#eval ~~~~ +
+ +_____________ + ## Approach: String, List, and Dictionary Methods diff --git a/exercises/practice/wordy/.docs/hints.md b/exercises/practice/wordy/.docs/hints.md new file mode 100644 index 0000000000..95e798f7db --- /dev/null +++ b/exercises/practice/wordy/.docs/hints.md @@ -0,0 +1,40 @@ +# Hints + +## General + +- This challenge is all about validating, "cleaning up", and processing the given question in the **_correct order_**. +- If you read the directions and tests carefully, you will find there are three conditions that need to be met for a question to be "valid". +If a question is not valid, you will need to [raise ][raise-statement] a [ValueError][value-error] with a message (_`ValueError("unknown operation")`_). + It is best if you get this particular check out of the way before doing anything else. +- Processing a question before calculating the answer is all about utilizing [string methods][str-methods]. +A few popular ones to investigate include `str.removeprefix`, `str.removesuffix`, `str.replace`, and `str.strip`. +Others you might want to check out are `str.startswith`, `str.endswith`, and `in` (_which can apply to more than just strings_). + +- It is possible to iterate over a string. However, it is **much** easier to iterate over a list of _words_ if the string you are processing is a sentence or fragment with spaces. [`str.split`][split] can break apart a string and return a list of "words". +- A [`while-loop`][while-loop] is very useful for iterating over the question to process items. +- For fewer error checks and cleaner error-handling, a [`try-except`][handling-exceptions] is recommended as you process the question. +- **Remember**: the question is processed **_left-to-right_**. That means "1 plus 12 minus 3 multiplied by 4" gets processed by: + - Calculating "1 plus 12" (13), + - Calculating "13 minus 3" (10), + - Calculating "10 multiplied by 4" (40). + - The result of the first calculation is _concatenated with the remainder of the question to form a new question for the next step_. + - This technique is sometimes called [the accumulator pattern][accumulator-pattern], or [fold][fold] / [foldl][foldl] in functional programming languages like [Haskell][haskell-folds] or Lisp. + - Python includes two methods that are purpose-built to apply the `accumulator-pattern` to iterable data structures like `lists`, `tuples`, and `strings`. + [`functools.reduce`][reduce] and [`itertools.accumulate`][accumulate] could be interesting to investigate here. + +- This exercise has many potential solutions and many paths you can take along the way. + No path is manifestly "better" than another, although a particular path may be more interesting or better suited to what you want to learn or explore right now. + + +[accumulate]: https://docs.python.org/3/library/itertools.html#itertools.accumulate +[accumulator-pattern]: https://muzny.github.io/csci1200-notes/08/2/accumulator.html +[fold]: https://en.wikipedia.org/wiki/Fold_(higher-order_function) +[foldl]: https://slim.computer/eecs-111-ta-guide/material/higher-order/Fold.html +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[haskell-folds]: https://www.ashwinnarayan.com/post/a-study-on-haskell-folds/ +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split +[str-methods]: https://docs.python.org/3/library/stdtypes.html#string-methods +[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError +[while-loop]: https://python-practice.com/learn/loops/while_loop/ From 7875b774154e9fe0fe9a830e41d0935ad083b2e7 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 15 Oct 2024 15:18:06 -0700 Subject: [PATCH 725/826] Fixed links and other lingering typos. (#3790) --- .../dunder-getattribute/content.md | 6 ++++- .../.approaches/functools-reduce/content.md | 4 +-- .../wordy/.approaches/introduction.md | 14 ++++++++-- .../wordy/.approaches/recursion/content.md | 27 ++++++++++++++----- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index 31c42e7a2a..3f82c0ee51 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -37,13 +37,17 @@ def answer(question): This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [`dunder-methods`][dunder] methods. Since only whole numbers are involved, the available `dunder-methods` are those for the [`int`][int] class/namespace. The supported methods for the `int()` namespace can be found by using `print(dir(int))` or `print(int.__dict__)` in a Python terminal. -See [SO: Difference between dir() and __dict__][dir-vs-__dict__] for differences between the two. +See [`SO: Difference between dir() and __dict__`][dir-vs-__dict__] for more details. + +
~~~~exercism/note The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of all valid attributes for an object. The `dunder-method` [`.__dict__`](https://docs.python.org/3/reference/datamodel.html#object.__dict__) is a mapping of an objects writable attributes. ~~~~ +
+ The `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. It indicates that the value should not be changed. diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md index 602a678ffd..8bb7de25f3 100644 --- a/exercises/practice/wordy/.approaches/functools-reduce/content.md +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -72,8 +72,9 @@ A `try-except` is not needed here because the error scenarios are already filter But it is also hard to understand what is happening if you have not worked with a reduce or foldl function in the past. It could be argued that writing the code as a `while-loop` or recursive function is easier to reason about for non-functional programmers. +
-## Variation: 1: Use a Dictionary of `lambdas` instead of importing from operator +## Variation 1: Use a Dictionary of `lambdas` instead of importing from operator The imports from operator can be swapped out for a dictionary of `lambda-expressions` (or calls to `dunder-methods`), if so desired. @@ -119,7 +120,6 @@ def answer(question): return result ``` - [approach-lambdas-in-a-dictionary]: https://exercism.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary [callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 8af7b35835..135af33b59 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -22,10 +22,12 @@ A whole class of error can be eliminated up front by checking if a question star Any other question formulation becomes a `ValueError("unknown operation")`. This could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. +
~~~~exercism/note There are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. -However, the solutions all follow these general steps: +However, solutions all follow the same general steps: + 1. Remove the parts of the question string that do not apply to calculating the answer. 2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations. @@ -38,6 +40,7 @@ However, the solutions all follow these general steps: 6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`. ~~~~ +
For question cleaning, [`str.removeprefix`][removeprefix] and [`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful: @@ -95,6 +98,7 @@ Some solutions use either [lambda][lambdas] expressions, [dunder/"special" metho However, the exercise can be solved without using `operator`, `lambdas`, `dunder-methods` or `eval`. It is recommended that you first start by solving it _without_ "advanced" strategies, and then refine your solution into something more compact or complex as you learn and practice. +
~~~~exercism/caution Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach. @@ -158,6 +162,7 @@ Alternatives could use a [dictionary][dict] to store word --> operator mappings For more details and variations, read the [String, List and Dictionary Methods][approach-string-list-and-dict-methods] approach. +
## Approach: Import Callables from the Operator Module @@ -199,6 +204,7 @@ Like the first approach, it uses a [try-except][handling-exceptions] block for h For more details and options, take a look at the [Import Callables from the Operator Module][approach-import-callables-from-operator] approach. +
## Approach: Regex and the Operator Module @@ -257,6 +263,7 @@ It is longer than some solutions, but clearer and potentially easier to maintain For more details, take a look at the [regex-with-operator-module][approach-regex-with-operator-module] approach. +
## Approach: Lambdas in a Dictionary to return Functions @@ -306,6 +313,7 @@ These "hand-crafted" `lambdas` could also introduce a mathematical error, althou For more details, take a look at the [Lambdas in a Dictionary][approach-lambdas-in-a-dictionary] approach. +
## Approach: Recursion @@ -351,6 +359,7 @@ The dictionary in this example could use functions from `operator`, `lambdas`, ` For more details, take a look at the [recursion][approach-recursion] approach. +
## Approach: functools.reduce() @@ -393,6 +402,7 @@ This solution may be a little less clear to follow or reason about due to the sl For more details and variations, take a look at the [functools.reduce for Calculation][approach-functools-reduce] approach. +
## Approach: Dunder methods with `__getattribute__` @@ -444,7 +454,7 @@ For more detail on this solution, take a look at the [dunder method with `__geta [approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute [approach-functools-reduce]: https://exercism.org/tracks/python/exercises/wordy/approaches/functools-reduce [approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator -[approach-lambdas-in-a-dictionary]: https://exercsim.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary +[approach-lambdas-in-a-dictionary]: https://exercism.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary [approach-recursion]: https://exercism.org/tracks/python/exercises/wordy/approaches/recursion [approach-regex-with-operator-module]: https://exercism.org/tracks/python/exercises/wordy/approaches/regex-with-operator-module [approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md index 8131cbb7c9..0ba69fca50 100644 --- a/exercises/practice/wordy/.approaches/recursion/content.md +++ b/exercises/practice/wordy/.approaches/recursion/content.md @@ -5,14 +5,21 @@ A recursive strategy [may not always be obvious][looping-vs-recursion] or easy — but it is always possible. So the `while-loop`s used in other approaches to Wordy can be re-written to use recursive calls. +
+ That being said, Python famously does not perform [tail-call optimization][tail-call-optimization], and limits recursive calls on the stack to a depth of 1000 frames, so it is important to only use recursion where you are confident that it can complete within the limit (_or something close to it_). [Memoization][memoization] and other strategies in [dynamic programming][dynamic-programming] can help to make recursion more efficient and "shorter" in Python, but it's always good to give it careful consideration. +
+ Recursion works best with problem spaces that resemble trees, include [backtracking][backtracking], or become progressively smaller. - Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-firs search][dfs], and [merge sort][merge-sort]. + Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-first search][dfs], and [merge sort][merge-sort]. + +
-Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and the [Bellman-Ford Algorithm][bellman-ford] lend themselves better to iteration. +Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and the [Bellman-Ford Algorithm][bellman-ford] lend themselves better to loops. +
```python from operator import add, mul, sub @@ -68,13 +75,16 @@ This approach separates the solution into three functions: 2. `clean()`, which takes a question string and returns a `list` of parsed words and numbers to calculate from. 3. `calculate()`, which performs the calculations on the `list` recursively, until a single number (_the base case check_) is returned as the answer — or an error is thrown. +
+ The cleaning logic is separate from the processing logic so that the cleaning steps aren't repeated over and over with each recursive `calculate()` call. -This separation also makes it easier to make changes in processing or calculating without creating conflict or confusion. +This separation also makes it easier to make changes without creating conflict or confusion. -Note that `calculate()` performs the same steps as the `while-loop` from [Import Callables from the Operator Module][approach-import-callables-from-operator] and others. +`calculate()` performs the same steps as the `while-loop` from [Import Callables from the Operator Module][approach-import-callables-from-operator] and others. The difference being that the `while-loop` test for `len()` 1 now occurs as an `if` condition in the function (_the base case_), and the "looping" is now a call to `calculate()` in the `else` condition. `calculate()` can also use many of the strategies detailed in other approaches, as long as they work with the recursion. +
`clean()` can also use any of the strategies detailed in other approaches, two of which are below: @@ -91,6 +101,7 @@ The difference being that the `while-loop` test for `len()` 1 now occurs as an ` question.strip("?").split())) #<-- The () in list() also invokes implicit concatenation. ``` +
## Variation 1: Use Regex for matching, cleaning, and calculating @@ -184,6 +195,7 @@ Because each new iteration of the question needs to be validated, there is an `i Note that the `for-loop` and VALIDATE use [`re.match`][re-match], but DIGITS validation uses [`re.fullmatch`][re-fullmatch]. +
## Variation 2: Use Regex, Recurse within the For-loop @@ -229,13 +241,14 @@ This saves some space, but requires that the nested `tuples` be unpacked as the Recursion is used a bit differently here from the previous variations — the calls are placed [within the `for-loop`][recursion-within-loops]. Because the regex are more generic, they will match a `digit-operation-digit` trio in a longer question, so the line `return operation(calculate(match['x']), calculate(match['y']))` is effectively splitting a question into parts that can then be worked on in their own stack frames. + For example: 1. "1 plus -10 multiplied by 13 divided by 2" would match on "1 plus -10" (_group x_) **multiplied by** "13 divided by 2" (_group y_). -2. This would then be re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` +2. This is re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` 3. At this point the loop would pause as the two recursive calls to `calculate()` spawn -4. The loops would run again — and so would the calls to `calculate()`, until there wasn't any match that caused a split of the question or an error. -5. One at a time, the numbers would then be returned, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` could be solved, at which point the answer would be returned. +4. The loops then run again — and so would the calls to `calculate()`, until there wasn't any match that caused a split of the question or an error. +5. One at a time, the numbers would then be returned, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` could be solved, at which point the answer is returned. For a more visual picture, you can step through the code on [pythontutor.com][recursion-in-loop-pythontutor]. From df381d86f49a7bee83e42cd467acbd0665b8752d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 15 Oct 2024 21:43:01 -0700 Subject: [PATCH 726/826] Touch ups and edits for clarity in instructions, introductions, hints and stub file. (#3791) --- concepts/basics/introduction.md | 23 +++++++--- .../guidos-gorgeous-lasagna/.docs/hints.md | 5 +- .../.docs/instructions.md | 46 ++++++++++++++----- .../.docs/introduction.md | 16 ++++--- .../guidos-gorgeous-lasagna/lasagna.py | 12 +++-- 5 files changed, 74 insertions(+), 28 deletions(-) diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index 2a874394eb..818dd47dea 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -2,18 +2,26 @@ Python is a [dynamic and strongly typed][dynamic typing in python] programming language. It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. +Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. + +Python was created by Guido van Rossum and first released in 1991. Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**. -Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. +We'll dig more into what all of that means as we continue through the Python track concepts. -Python was created by Guido van Rossum and first released in 1991. +This first concept (`basics`) introduces 4 major Python language features: +1. Name Assignment (_variables and constants_), +2. Functions (_the `def` keyword and the `return` keyword_), +3. Comments, and +4. Docstrings. +
## Name Assignment (Variables & Constants) Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: ` = `. -A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. +A name can be reassigned (or re-bound) to different values (different object types) over its lifetime: ```python @@ -37,9 +45,10 @@ A name can be reassigned (or re-bound) to different values (different object typ ### Constants -Constants are names meant to be assigned only once in a program. -They should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in the program. -Using `SCREAMING_SNAKE_CASE` signals that the name should not be re-assigned, or its value mutated. +Constants are names meant to be assigned only once in a program — although Python will not prevent re-assignment. +Using `SCREAMING_SNAKE_CASE` signals to anyone reading the code that the name should **not** be re-assigned, or its value mutated. +Constants should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in a program. + ## Functions @@ -92,7 +101,9 @@ def add_two_numbers(number_one, number_two): 11 ``` + Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +This means that if you do not use `return` in a function, Python will return the `None` object for you. The details of `None` will be covered in a later exercise. For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md index 9006524852..f2c07d50b6 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md @@ -10,7 +10,9 @@ ## 1. Define expected bake time in minutes -- You need to [name][naming] a constant, and [assign][assignment] it an [integer][numbers] value. +- You need to [name][naming] a [constant][constants], and [assign][assignment] it an [integer][numbers] value. + This constant should be the first thing after the docstring that is at the top of the file. + Remember to remove the #TODO comment after defining the constant. ## 2. Calculate remaining bake time in minutes @@ -38,6 +40,7 @@ [assignment]: https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment-stmt [comments]: https://realpython.com/python-comments-guide/ +[constants]: https://stackoverflow.com/a/2682752 [defining functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [docstrings]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings [naming]: https://realpython.com/python-variables/ diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md index 2d61cec837..fce16c4679 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md @@ -4,52 +4,70 @@ You're going to write some code to help you cook a gorgeous lasagna from your fa You have five tasks, all related to cooking your recipe. -## 1. Define expected bake time in minutes +
-Define an `EXPECTED_BAKE_TIME` constant that returns how many minutes the lasagna should bake in the oven. +~~~~exercism/note +We have started the first function definition for you in the stub file, but you will need to write the remaining function definitions yourself. +You will also need to define any constants yourself. +Read the #TODO comment lines in the stub file carefully. +Once you are done with a task, remove the TODO comment. +~~~~ + +
+ +## 1. Define expected bake time in minutes as a constant + +Define the `EXPECTED_BAKE_TIME` [constant][constants] that represents how many minutes the lasagna should bake in the oven. According to your cookbook, the Lasagna should be in the oven for 40 minutes: ```python ->>> import lasagna ->>> lasagna.EXPECTED_BAKE_TIME +>>> print(EXPECTED_BAKE_TIME) 40 ``` ## 2. Calculate remaining bake time in minutes -Implement the `bake_time_remaining()` function that takes the actual minutes the lasagna has been in the oven as an argument and returns how many minutes the lasagna still needs to bake based on the `EXPECTED_BAKE_TIME`. +Complete the `bake_time_remaining()` function that takes the actual minutes the lasagna has been in the oven as an argument and returns how many minutes the lasagna still needs to bake based on the `EXPECTED_BAKE_TIME` constant. ```python ->>> from lasagna import bake_time_remaining >>> bake_time_remaining(30) 10 ``` + ## 3. Calculate preparation time in minutes -Implement the `preparation_time_in_minutes(number_of_layers)` function that takes the number of layers you want to add to the lasagna as an argument and returns how many minutes you would spend making them. +Define the `preparation_time_in_minutes()` [function][functions] that takes the `number_of_layers` you want to add to the lasagna as an argument and returns how many minutes you would spend making them. Assume each layer takes 2 minutes to prepare. ```python ->>> from lasagna import preparation_time_in_minutes +>>> def preparation_time_in_minutes(number_of_layers): + ... + ... + >>> preparation_time_in_minutes(2) 4 ``` + ## 4. Calculate total elapsed cooking time (prep + bake) in minutes -Implement the `elapsed_time_in_minutes(number_of_layers, elapsed_bake_time)` function that has two parameters: `number_of_layers` (_the number of layers added to the lasagna_) and `elapsed_bake_time` (_the number of minutes the lasagna has been baking in the oven_). -This function should return the total number of minutes you've been cooking, or the sum of your preparation time and the time the lasagna has already spent baking in the oven. +Define the `elapsed_time_in_minutes()` function that takes two parameters as arguments: `number_of_layers` (_the number of layers added to the lasagna_) and `elapsed_bake_time` (_the number of minutes the lasagna has been baking in the oven_). +This function should return the total number of minutes you have been cooking, or the sum of your preparation time and the time the lasagna has already spent baking in the oven. ```python ->>> from lasagna import elapsed_time_in_minutes +>>> def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): + ... + ... + >>> elapsed_time_in_minutes(3, 20) 26 ``` + ## 5. Update the recipe with notes -Go back through the recipe, adding "notes" in the form of function docstrings. +Go back through the recipe, adding "notes" in the form of [function docstrings][function-docstrings]. ```python def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): @@ -64,3 +82,7 @@ def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): lasagna. """ ``` + +[constants]: https://stackoverflow.com/a/2682752 +[functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions +[function-docstrings]: https://docs.python.org/3/tutorial/controlflow.html#documentation-strings diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index ffe2bedd6a..20321da530 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -14,6 +14,7 @@ This first exercise introduces 4 major Python language features: 3. Comments, and 4. Docstrings. +
~~~~exercism/note @@ -25,6 +26,7 @@ On the Python track, [variables][variables] are always written in [`snake_case`] [snake case]: https://en.wikipedia.org/wiki/Snake_case ~~~~ +
## Name Assignment (Variables & Constants) @@ -33,8 +35,8 @@ A name can be reassigned (or re-bound) to different values (different object typ ```python ->>> my_first_variable = 1 # my_first_variable bound to an integer object of value one. ->>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2. +>>> my_first_variable = 1 #<-- my_first_variable bound to an integer object of value one. +>>> my_first_variable = 2 #<-- my_first_variable re-assigned to integer value 2. >>> print(type(my_first_variable)) @@ -42,12 +44,12 @@ A name can be reassigned (or re-bound) to different values (different object typ >>> print(my_first_variable) 2 ->>> my_first_variable = "Now, I'm a string." # You may re-bind a name to a different object type and value. +>>> my_first_variable = "Now, I'm a string." #<-- You may re-bind a name to a different object type and value. >>> print(type(my_first_variable)) >>> print(my_first_variable) -"Now, I'm a string." # Strings can be declared using single or double quote marks. +"Now, I'm a string." #<-- Strings can be declared using single or double quote marks. ``` @@ -90,10 +92,11 @@ IndentationError: unindent does not match any outer indentation level Functions _explicitly_ return a value or object via the [`return`][return] keyword: + ```python # Function definition on first line, explicit return used on final line. -def add_two_numbers(number_one, number_two): - return number_one + number_two +>>> def add_two_numbers(number_one, number_two): + return number_one + number_two # Calling the function in the Python terminal returns the sum of the numbers. @@ -109,6 +112,7 @@ def add_two_numbers(number_one, number_two): Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +This means that if you do not use `return` in a function, Python will return the `None` object for you. The details of `None` will be covered in a later exercise. For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py index 90d0102584..a3df0ab2da 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py @@ -8,7 +8,7 @@ """ -#TODO: define the 'EXPECTED_BAKE_TIME' constant. +#TODO: define the 'EXPECTED_BAKE_TIME' constant below. #TODO: Remove 'pass' and complete the 'bake_time_remaining()' function below. @@ -27,9 +27,15 @@ def bake_time_remaining(): #TODO: Define the 'preparation_time_in_minutes()' function below. -# You might also consider using 'PREPARATION_TIME' here, if you have it defined. +# You might also consider defining a 'PREPARATION_TIME' constant. +# You can do that on the line below the 'EXPECTED_BAKE_TIME' constant. +# This will make it easier to do calculations. #TODO: define the 'elapsed_time_in_minutes()' function below. -# Remember to add a docstring (you can copy and then alter the one from bake_time_remaining.) + + + +# TODO: Remember to go back and add docstrings to all your functions +# (you can copy and then alter the one from bake_time_remaining.) From c230448e3f9a2aefd3b0198c9fad8d6efc3b09c2 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 16 Oct 2024 00:13:48 -0700 Subject: [PATCH 727/826] Fixed final test and error message. Sometimes, I am an idiot. (#3792) [no important files changed] The test has changed, but this will allow more passing code without need for retest. --- exercises/concept/plane-tickets/generators_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/generators_test.py b/exercises/concept/plane-tickets/generators_test.py index 12a3532aa5..1596d424ed 100644 --- a/exercises/concept/plane-tickets/generators_test.py +++ b/exercises/concept/plane-tickets/generators_test.py @@ -134,4 +134,7 @@ def test_generate_codes(self): f'The function returned {actual_result}, but the tests ' f'expected {expected} when generating ticket numbers.') - self.assertEqual(list(generate_codes(seat_numbers, flight_id)), expected, msg=error_message) + # Note: DO NOT call the function here again, in case the student is using list.pop() + # to process the input. If another call is done with that condition, + # the test will fail with a terrible error message. + self.assertEqual(actual_result, expected, msg=error_message) From 1d2c981fc0e80632bcf64269f994f89c9fdab581 Mon Sep 17 00:00:00 2001 From: Wolfgang <53096914+wolmoe@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:28:40 +0200 Subject: [PATCH 728/826] Fixed typo.md (#3794) Fixed minor typo hasable -> hashable --- exercises/concept/cater-waiter/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 044432b5c2..926aea3d90 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -1,7 +1,7 @@ # Sets -A [set][type-set] is a _mutable_ and _unordered_ collection of [_hasable_][hashable] objects. +A [set][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. Set members must be distinct — duplicate items are not allowed. They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. From 83e71ed61e2110e62e0f7a9e959bfd8cdff3ec7a Mon Sep 17 00:00:00 2001 From: Andrew Pam Date: Wed, 23 Oct 2024 19:50:00 +1100 Subject: [PATCH 729/826] Update resistor_color_expert_test.py (#3797) Add additional tests for some edge cases. Note that "1 ohms", "1 kiloohms" and "1 megaohms" should really be "ohm" singular but that's out of scope of the requirements for this exercise. --- .../resistor_color_expert_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py index 2ba4b877d9..47e7fc6344 100644 --- a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py +++ b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py @@ -49,3 +49,13 @@ def test_brown_red_orange_green_and_blue(self): self.assertEqual( resistor_label(["brown", "red", "orange", "green", "blue"]), "12.3 megaohms ±0.25%" ) + + def test_brown_black_brown_yellow_and_violet(self): + self.assertEqual( + resistor_label(["brown", "black", "brown", "yellow", "violet"]), "1.01 megaohms ±0.1%" + ) + + def test_brown_black_red_and_red(self): + self.assertEqual( + resistor_label(["brown", "black", "red", "red"]), "1 kiloohms ±2%" + ) From 6a7a35111da194b5e9f90903897fd70267211152 Mon Sep 17 00:00:00 2001 From: Heitor de Bittencourt Date: Wed, 23 Oct 2024 16:24:16 -0300 Subject: [PATCH 730/826] [Mecha Munch & Dict Methods Concept]: Fix Typo in Introduction & About Files (#3796) * Fix typo in dict methods introduction * Fix typo in dict-methods concept --- concepts/dict-methods/about.md | 2 +- exercises/concept/mecha-munch-management/.docs/introduction.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/dict-methods/about.md b/concepts/dict-methods/about.md index d910d3e916..6dcf9b4ae7 100644 --- a/concepts/dict-methods/about.md +++ b/concepts/dict-methods/about.md @@ -214,7 +214,7 @@ When both dictionaries share keys, `dict_two` values take precedence. 'Purple baseline': '#161748'} ``` -`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: +`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: ```python >>> palette_III = {'Grassy Green': (155, 196, 0), diff --git a/exercises/concept/mecha-munch-management/.docs/introduction.md b/exercises/concept/mecha-munch-management/.docs/introduction.md index 983b905c27..17d6748771 100644 --- a/exercises/concept/mecha-munch-management/.docs/introduction.md +++ b/exercises/concept/mecha-munch-management/.docs/introduction.md @@ -191,7 +191,7 @@ When both dictionaries share keys, `dict_two` values take precedence. 'Purple baseline': '#161748'} ``` -`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: +`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: ```python >>> palette_III = {'Grassy Green': (155, 196, 0), From 5f70c9d4ee61faed488e789bc6449fa347f9f778 Mon Sep 17 00:00:00 2001 From: Luke McGuire Date: Wed, 23 Oct 2024 19:30:28 -0500 Subject: [PATCH 731/826] test: Update Chaitana's tests to verify queue has been updated (#3799) --- .../list_methods_test.py | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py index c519ff255c..5df2af327b 100644 --- a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py +++ b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py @@ -140,14 +140,39 @@ def test_how_many_namefellows(self): @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): - test_data = ['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'] - actual_result = remove_the_last_person(test_data) - expected = 'Rocket' - error_message = (f'Called remove_the_last_person({test_data}).' - f'The function returned {actual_result}, but the tests ' - f'expected {expected} as the person who was removed.') + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Rocket'), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], 'Ultron'), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], 'Natasha') + ] + for variant, (input, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', input=input, expected=expected): + actual_result = remove_the_last_person(input) + expected_result = expected + + error_message = (f'Called remove_the_last_person({input}).' + f'The function returned {actual_result}, but the tests expected {expected_result}.') + + self.assertEqual(actual_result, expected_result, msg=error_message) + + @pytest.mark.task(taskno=6) + def test_remove_the_last_person_validate_queue(self): + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha']), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket']), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron']) + ] + for variant, (input, modified) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', input=input, modified=modified): + unmodified = deepcopy(input) + actual_result = remove_the_last_person(input) + expected_queue = modified + + error_message = (f'\nCalled remove_the_last_person({unmodified}).\n' + f'The function was expected to change the queue to {expected_queue},\n' + f'but the queue looks like {input} instead.') - self.assertIs(actual_result, expected, msg=error_message) + self.assertEqual(input, expected_queue, msg=error_message) @pytest.mark.task(taskno=7) def test_sorted_names(self): From 7993df720af847fb12b8051de9739a0dba70dbab Mon Sep 17 00:00:00 2001 From: Ryan Souza <76923948+Ryrden@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:39:18 -0300 Subject: [PATCH 732/826] fix: wrong reference (#3800) --- exercises/practice/acronym/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/acronym/.approaches/introduction.md b/exercises/practice/acronym/.approaches/introduction.md index 38b606b4a2..9aaac23d6f 100644 --- a/exercises/practice/acronym/.approaches/introduction.md +++ b/exercises/practice/acronym/.approaches/introduction.md @@ -157,4 +157,4 @@ To compare performance of the approaches, take a look at the [Performance articl [approach-map-function]: https://exercism.org/tracks/python/exercises/acronym/approaches/map-function [approach-regex-join]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-join [approach-regex-sub]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-sub -[article-performance]: https://exercism.org/tracks/python/exercises/isogram/articles/performance +[article-performance]: https://exercism.org/tracks/python/exercises/acronym/articles/performance From 694a421e0276970e14e6b4ce306f51770fd84f56 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:13:39 -0700 Subject: [PATCH 733/826] what I hope is the final pass of the wordy typo monsters. (#3803) --- .../wordy/.approaches/functools-reduce/content.md | 12 +++++++----- exercises/practice/wordy/.approaches/introduction.md | 6 +++--- .../practice/wordy/.approaches/recursion/content.md | 6 +++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md index 8bb7de25f3..8bc42449fa 100644 --- a/exercises/practice/wordy/.approaches/functools-reduce/content.md +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -16,16 +16,17 @@ def answer(question): raise ValueError("unknown operation") # Using the built-in filter() to clean & split the question.. - list(filter(lambda x: - x not in ("What", "is", "by"), - question.strip("?").split())) + question = list(filter(lambda x: + x not in ("What", "is", "by"), + question.strip("?").split())) # Separate candidate operators and numbers into two lists. operations = question[1::2] # Convert candidate elements to int(), checking for "-". # All other values are replaced with None. - digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + digits = [int(element) if + (element.isdigit() or element[1:].isdigit()) else None for element in question[::2]] # If there is a mis-match between operators and numbers, toss error. @@ -106,7 +107,8 @@ def answer(question): # Convert candidate elements to int(), checking for "-". # All other values are replaced with None. - digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + digits = [int(element) if + (element.isdigit() or element[1:].isdigit()) else None for element in question[::2]] # If there is a mis-match between operators and numbers, toss error. diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 135af33b59..c6d7ed8927 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -31,12 +31,12 @@ However, solutions all follow the same general steps: 1. Remove the parts of the question string that do not apply to calculating the answer. 2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations. - -- _Converting the question string into a `list` of words is hugely helpful here._ + — _Converting the question string into a `list` of words is hugely helpful here._ 3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to the mathematical operations +, -, *, and /. 4. Apply the operation to the numbers, which should result in a single number. - -- _Employing a `try-except` block can trap any errors thrown and make the code both "safer" and less complex._ + — _Employing a `try-except` block can trap any errors thrown and make the code both "safer" and less complex._ 5. Use the calculated number from step 4 as the start for the next "trio" (_number, operation, number_) in the question. The calculated number + the remainder of the question becomes the question being worked on in the next iteration. - -- _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ + — _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ 6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`. ~~~~ diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md index 0ba69fca50..794f1b41c1 100644 --- a/exercises/practice/wordy/.approaches/recursion/content.md +++ b/exercises/practice/wordy/.approaches/recursion/content.md @@ -246,9 +246,9 @@ For example: 1. "1 plus -10 multiplied by 13 divided by 2" would match on "1 plus -10" (_group x_) **multiplied by** "13 divided by 2" (_group y_). 2. This is re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` -3. At this point the loop would pause as the two recursive calls to `calculate()` spawn -4. The loops then run again — and so would the calls to `calculate()`, until there wasn't any match that caused a split of the question or an error. -5. One at a time, the numbers would then be returned, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` could be solved, at which point the answer is returned. +3. At this point, the loop pauses as the two recursive calls to `calculate()` spawn +4. The loop runs again — and so do the calls to `calculate()` — until there isn't any match that splits the question or any errors. +5. One at a time, the numbers are returned from the `calculate()` calls on the stack, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` is solved, at which point the answer is returned. For a more visual picture, you can step through the code on [pythontutor.com][recursion-in-loop-pythontutor]. From dc66e5964c60d70259bfbc4040dcbbcfa1655fa3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:34:29 -0700 Subject: [PATCH 734/826] Fixed spelling errors in approaches docs. (#3804) --- .../acronym/.approaches/generator-expression/content.md | 2 +- .../practice/acronym/.approaches/list-comprehension/content.md | 2 +- exercises/practice/acronym/.approaches/regex-join/content.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/acronym/.approaches/generator-expression/content.md b/exercises/practice/acronym/.approaches/generator-expression/content.md index f5b590ccaa..47ec9aa8f8 100644 --- a/exercises/practice/acronym/.approaches/generator-expression/content.md +++ b/exercises/practice/acronym/.approaches/generator-expression/content.md @@ -30,7 +30,7 @@ A [`generator-expression`][generator-expression] is then used to iterate through Generator expressions are short-form [generators][generators] - lazy iterators that produce their values _on demand_, instead of saving them to memory. This generator expression is consumed by [`str.join()`][str-join], which joins the generated letters together using an empty string. -Other "seperator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. +Other "separator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. Since the generator expression and `join()` are fairly succinct, they are put directly on the `return` line rather than assigning and returning an intermediate variable for the acronym. diff --git a/exercises/practice/acronym/.approaches/list-comprehension/content.md b/exercises/practice/acronym/.approaches/list-comprehension/content.md index 3a7b0cd40f..7e98f45c74 100644 --- a/exercises/practice/acronym/.approaches/list-comprehension/content.md +++ b/exercises/practice/acronym/.approaches/list-comprehension/content.md @@ -25,7 +25,7 @@ As of this writing, both of these methods benchmark slower than using `str.repla A [`list comprehension`][list comprehension] is then used to iterate through the phrase and select the first letters of each word via [`bracket notation`][subscript notation]. This comprehension is passed into [`str.join()`][str-join], which unpacks the `list` of first letters and joins them together using an empty string - the acronym. -Other "seperator" strings besides an empty string can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. +Other "separator" strings besides an empty string can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. Since the comprehension and `join()` are fairly succinct, they are put directly on the `return` line rather than assigning and returning an intermediate variable for the acronym. diff --git a/exercises/practice/acronym/.approaches/regex-join/content.md b/exercises/practice/acronym/.approaches/regex-join/content.md index f6ca2f4844..227ba06d5e 100644 --- a/exercises/practice/acronym/.approaches/regex-join/content.md +++ b/exercises/practice/acronym/.approaches/regex-join/content.md @@ -74,7 +74,7 @@ Note that when using `finditer()`, the `Match object` has to be unpacked via `ma Generator expressions are short-form [generators][generators] - lazy iterators that produce their values _on demand_, instead of saving them to memory. This generator expression is consumed by [`str.join()`][str-join], which joins the generated letters together using an empty string. -Other "seperator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. +Other "separator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. Finally, the result of `.join()` is capitalized using the [chained][chaining] [`.upper()`][str-upper]. From abd31ba96e0822d35fe67ba965bbe457f9df2437 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:41:21 -0700 Subject: [PATCH 735/826] Initialzed - initialized spelling corrections. (#3805) --- exercises/practice/luhn/.approaches/recursion/content.md | 2 +- .../luhn/.approaches/replace-reverse-enumerate/content.md | 2 +- exercises/practice/luhn/.approaches/reversed-for/content.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/luhn/.approaches/recursion/content.md b/exercises/practice/luhn/.approaches/recursion/content.md index 6bb89e7932..a84c547851 100644 --- a/exercises/practice/luhn/.approaches/recursion/content.md +++ b/exercises/practice/luhn/.approaches/recursion/content.md @@ -29,7 +29,7 @@ class Luhn: ``` -The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The `Luhn` object is initialized with the `card_num` value, which is the number to be validated with the Luhn algorithm. The result of the validation is returned from `Luhn`'s `valid()` method. In this approach, a member variable is set to the result of running the Luhn algorithm. That variable is returned from the `valid()` method. diff --git a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md index fd4e49b12c..fe0b697b31 100644 --- a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md +++ b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md @@ -29,7 +29,7 @@ class Luhn: ``` -The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The `Luhn` object is initialized with the `card_num` value, which is the number to be validated with the Luhn algorithm. The result of the validation is returned from `Luhn`'s `valid()` method. In this approach, a member variable is set to the result of running the Luhn algorithm. That variable is returned from the `valid()` method. diff --git a/exercises/practice/luhn/.approaches/reversed-for/content.md b/exercises/practice/luhn/.approaches/reversed-for/content.md index abf5a591ca..683fca3048 100644 --- a/exercises/practice/luhn/.approaches/reversed-for/content.md +++ b/exercises/practice/luhn/.approaches/reversed-for/content.md @@ -30,7 +30,7 @@ class Luhn: ``` -The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The `Luhn` object is initialized with the `card_num` value, which is the number to be validated with the Luhn algorithm. The result of the validation is returned from `Luhn`'s `valid()` method. In this approach, a member variable is set to the result of running the Luhn algorithm. That variable is returned from the `valid()` method. From f93b0ddcc6b92bce8f1a2aa8b70f899bb09d91ea Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:45:58 -0700 Subject: [PATCH 736/826] Seperate to separate typo fix. (#3806) --- concepts/classes/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/classes/about.md b/concepts/classes/about.md index f88ce892f3..11b0364354 100644 --- a/concepts/classes/about.md +++ b/concepts/classes/about.md @@ -118,7 +118,7 @@ class MyClass: def __init__(self, location): # This is an instance or object property, attribute, or variable. - # Note that we are unpacking the tuple argument into two seperate instance variables. + # Note that we are unpacking the tuple argument into two separate instance variables. self.location_x = location[0] self.location_y = location[1] From 5ad6668ac2e667e23342084cf34cfbcd17d68fd8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:52:28 -0700 Subject: [PATCH 737/826] Fix spelling error and heading in instruction.append file. (#3807) --- exercises/practice/linked-list/.docs/instructions.append.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/linked-list/.docs/instructions.append.md b/exercises/practice/linked-list/.docs/instructions.append.md index 00032862c2..25f30a1993 100644 --- a/exercises/practice/linked-list/.docs/instructions.append.md +++ b/exercises/practice/linked-list/.docs/instructions.append.md @@ -5,7 +5,7 @@ While linked lists can be implemented in a variety of ways with a variety of underlying data structures, we ask here that you implement your linked list in an OOP fashion. In the stub file, you will see the start of a `Node` class, as well as a `LinkedList` class. -Your `Node` class should keep track of its value, as well as which other nodes preceed or follow. +Your `Node` class should keep track of its value, as well as which nodes precede or follow. Your `push`, `pop`, `shift`, `unshift`, and the special method for `len` should be implemented in the `LinkedList` class. You might also find it useful to implement a special `iter` method for iteration. @@ -17,7 +17,7 @@ If the value appears more than once, only the **first** occurrence should be rem
-## Exception messages +## Exception Messages Sometimes it is necessary to [raise an exception][raising]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. From bfc578ecdd4369ccd798135780fec6be5cf3bf15 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 15:02:34 -0700 Subject: [PATCH 738/826] fixed typo in itertools compress approach. (#3808) --- .../raindrops/.approaches/itertools-compress/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/raindrops/.approaches/itertools-compress/content.md b/exercises/practice/raindrops/.approaches/itertools-compress/content.md index b5da476862..42e02c188e 100644 --- a/exercises/practice/raindrops/.approaches/itertools-compress/content.md +++ b/exercises/practice/raindrops/.approaches/itertools-compress/content.md @@ -19,7 +19,7 @@ If the 'sounds' string is empty, a string version of the number is returned inst This is very succinct code that avoids string concatenation. However, it does require the overhead of importing `compress()` from the [itertools][itertools] module. The code is also harder to maintain should there be additional factors/sounds needed. -Because the factors and sounds are seperated, there is a chance mistakes could be made like forgetting a number or swapping which factor is paired with which sound. +Because the factors and sounds are separated, there is a chance mistakes could be made like forgetting a number or swapping which factor is paired with which sound. A better approach for maintenance might be to turn the 'sounds' `tuple` into a dictionary where the factors and sounds can be stored separate from the logic that does the calculations and string creation: From 42bbbfd2af2c2eb8ea999110a4138e99f6347a70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:06:20 -0700 Subject: [PATCH 739/826] Bump actions/checkout from 4.2.0 to 4.2.2 (#3810) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.0 to 4.2.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/d632683dd7b4114ad314bca15554477dd762a938...11bd71901bbe5b1630ceea73d27597364c9af683) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index c895b2ae2a..af069321ce 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index d4434c808b..46319fa611 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 9faae798e2..88c348a366 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Run test-runner run: docker compose run test-runner From da94511af8ab41cabbd4bf1222d6df714219204d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:06:54 -0700 Subject: [PATCH 740/826] Bump actions/setup-python from 5.2.0 to 5.3.0 (#3809) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.2.0 to 5.3.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/f677139bbe7f9c59b41e40162b753c062f5d49a3...0b93645e9fea7318ecaed2b359559ac225c90a2b) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index af069321ce..8c9d9f7fb6 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b with: python-version: ${{ matrix.python-version }} From e8b71922430e0d675956087cb482665f1025ca00 Mon Sep 17 00:00:00 2001 From: Andrew Pam Date: Tue, 5 Nov 2024 06:38:14 +1100 Subject: [PATCH 741/826] Update instructions.append.md (#3812) * Update instructions.append.md Clarify that the built-in exponentiation operator ** is equivalent to pow() and therefore doesn't demonstrate an implementation of a square root algorithm, and also note that for the domain of this exercise there are available solutions using only positive integers. * Update exercises/practice/square-root/.docs/instructions.append.md Adhere to one sentence per line convention. Co-authored-by: BethanyG --------- Co-authored-by: BethanyG --- exercises/practice/square-root/.docs/instructions.append.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exercises/practice/square-root/.docs/instructions.append.md b/exercises/practice/square-root/.docs/instructions.append.md index 84b4cf8ee7..eab4f0ac65 100644 --- a/exercises/practice/square-root/.docs/instructions.append.md +++ b/exercises/practice/square-root/.docs/instructions.append.md @@ -3,10 +3,11 @@ ## How this Exercise is Structured in Python -Python offers a wealth of mathematical functions in the form of the [math module][math-module] and built-ins such as [`pow()`][pow] and [`sum()`][sum]. +Python offers a wealth of mathematical functions in the form of the [math module][math-module] and built-ins such as the exponentiation operator `**`, [`pow()`][pow] and [`sum()`][sum]. However, we'd like you to consider the challenge of solving this exercise without those built-ins or modules. -While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of having only [natural numbers][nautral-number] (positive integers) as solutions. +While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of having only [natural numbers][nautral-number] (positive integers) as solutions. +It is possible to compute the square root of any natural number using only natural numbers in the computation. [math-module]: https://docs.python.org/3/library/math.html From d7a8e5b8d130c6a27376458344b92be0bb26639b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 4 Nov 2024 15:51:06 -0800 Subject: [PATCH 742/826] Updated metadata and instructions from problem specs Nov maintenence. (#3813) --- exercises/practice/hamming/.docs/instructions.md | 10 +++++----- exercises/practice/hamming/.meta/config.json | 2 +- exercises/practice/luhn/.docs/instructions.md | 3 ++- exercises/practice/pov/.meta/config.json | 2 +- .../practice/rna-transcription/.docs/instructions.md | 6 +++--- exercises/practice/sublist/.docs/instructions.md | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index 020fdd02d4..b9ae6efc51 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Calculate the Hamming Distance between two DNA strands. +Calculate the Hamming distance between two DNA strands. Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. @@ -9,18 +9,18 @@ In fact, the average human body experiences about 10 quadrillion cell divisions When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. -This is known as the "Hamming Distance". +This is known as the "Hamming distance". -We read DNA using the letters C,A,G and T. +We read DNA using the letters C, A, G and T. Two strands might look like this: GAGCCTACTAACGGGAT CATCGTAATGACGGCCT ^ ^ ^ ^ ^ ^^ -They have 7 differences, and therefore the Hamming Distance is 7. +They have 7 differences, and therefore the Hamming distance is 7. -The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) +The Hamming distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) ## Implementation notes diff --git a/exercises/practice/hamming/.meta/config.json b/exercises/practice/hamming/.meta/config.json index 1280ded912..117a2d954d 100644 --- a/exercises/practice/hamming/.meta/config.json +++ b/exercises/practice/hamming/.meta/config.json @@ -31,7 +31,7 @@ ".meta/example.py" ] }, - "blurb": "Calculate the Hamming difference between two DNA strands.", + "blurb": "Calculate the Hamming distance between two DNA strands.", "source": "The Calculating Point Mutations problem at Rosalind", "source_url": "https://rosalind.info/problems/hamm/" } diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 8cbe791fc2..49934c1064 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -22,7 +22,8 @@ The first step of the Luhn algorithm is to double every second digit, starting f We will be doubling ```text -4_3_ 3_9_ 0_4_ 6_6_ +4539 3195 0343 6467 +↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` If doubling the number results in a number greater than 9 then subtract 9 from the product. diff --git a/exercises/practice/pov/.meta/config.json b/exercises/practice/pov/.meta/config.json index cdc96ff934..44527ba940 100644 --- a/exercises/practice/pov/.meta/config.json +++ b/exercises/practice/pov/.meta/config.json @@ -22,5 +22,5 @@ }, "blurb": "Reparent a graph on a selected node.", "source": "Adaptation of exercise from 4clojure", - "source_url": "https://www.4clojure.com/" + "source_url": "https://github.com/oxalorg/4ever-clojure" } diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index 36da381f5a..4dbfd3a271 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -1,12 +1,12 @@ # Instructions -Your task is determine the RNA complement of a given DNA sequence. +Your task is to determine the RNA complement of a given DNA sequence. Both DNA and RNA strands are a sequence of nucleotides. -The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**) and thymine (**T**). +The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**), and thymine (**T**). -The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**) and uracil (**U**). +The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**), and uracil (**U**). Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement: diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md index 7535931afa..8228edc6ce 100644 --- a/exercises/practice/sublist/.docs/instructions.md +++ b/exercises/practice/sublist/.docs/instructions.md @@ -8,8 +8,8 @@ Given any two lists `A` and `B`, determine if: - None of the above is true, thus lists `A` and `B` are unequal Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. -List `A` is a superlist of `B` if `A` contains a sub-sequence of values equal to `B`. -List `A` is a sublist of `B` if `B` contains a sub-sequence of values equal to `A`. +List `A` is a superlist of `B` if `A` contains a contiguous sub-sequence of values equal to `B`. +List `A` is a sublist of `B` if `B` contains a contiguous sub-sequence of values equal to `A`. Examples: From dcc052c730b178c53b1170039e56b6fbed941390 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 4 Nov 2024 16:09:36 -0800 Subject: [PATCH 743/826] Updtated tests for Forth from problem specs. (#3814) --- exercises/practice/forth/.meta/tests.toml | 18 ++++++++++++++++++ exercises/practice/forth/forth_test.py | 20 +++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/exercises/practice/forth/.meta/tests.toml b/exercises/practice/forth/.meta/tests.toml index 16e0ffd9a6..5b5c09e240 100644 --- a/exercises/practice/forth/.meta/tests.toml +++ b/exercises/practice/forth/.meta/tests.toml @@ -24,6 +24,9 @@ description = "addition -> errors if there is nothing on the stack" [06efb9a4-817a-435e-b509-06166993c1b8] description = "addition -> errors if there is only one value on the stack" +[1e07a098-c5fa-4c66-97b2-3c81205dbc2f] +description = "addition -> more than two values on the stack" + [09687c99-7bbc-44af-8526-e402f997ccbf] description = "subtraction -> can subtract two numbers" @@ -33,6 +36,9 @@ description = "subtraction -> errors if there is nothing on the stack" [b3cee1b2-9159-418a-b00d-a1bb3765c23b] description = "subtraction -> errors if there is only one value on the stack" +[2c8cc5ed-da97-4cb1-8b98-fa7b526644f4] +description = "subtraction -> more than two values on the stack" + [5df0ceb5-922e-401f-974d-8287427dbf21] description = "multiplication -> can multiply two numbers" @@ -42,6 +48,9 @@ description = "multiplication -> errors if there is nothing on the stack" [8ba4b432-9f94-41e0-8fae-3b3712bd51b3] description = "multiplication -> errors if there is only one value on the stack" +[5cd085b5-deb1-43cc-9c17-6b1c38bc9970] +description = "multiplication -> more than two values on the stack" + [e74c2204-b057-4cff-9aa9-31c7c97a93f5] description = "division -> can divide two numbers" @@ -57,12 +66,21 @@ description = "division -> errors if there is nothing on the stack" [d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d] description = "division -> errors if there is only one value on the stack" +[f224f3e0-b6b6-4864-81de-9769ecefa03f] +description = "division -> more than two values on the stack" + [ee28d729-6692-4a30-b9be-0d830c52a68c] description = "combined arithmetic -> addition and subtraction" [40b197da-fa4b-4aca-a50b-f000d19422c1] description = "combined arithmetic -> multiplication and division" +[f749b540-53aa-458e-87ec-a70797eddbcb] +description = "combined arithmetic -> multiplication and addition" + +[c8e5a4c2-f9bf-4805-9a35-3c3314e4989a] +description = "combined arithmetic -> addition and multiplication" + [c5758235-6eef-4bf6-ab62-c878e50b9957] description = "dup -> copies a value on the stack" diff --git a/exercises/practice/forth/forth_test.py b/exercises/practice/forth/forth_test.py index f8402bdd64..1489bbd7df 100644 --- a/exercises/practice/forth/forth_test.py +++ b/exercises/practice/forth/forth_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-11-04 import unittest @@ -36,6 +36,9 @@ def test_addition_errors_if_there_is_only_one_value_on_the_stack(self): str(err.exception.args[0]), "Insufficient number of items in stack" ) + def test_addition_more_than_two_values_on_the_stack(self): + self.assertEqual(evaluate(["1 2 3 +"]), [1, 5]) + def test_subtraction_can_subtract_two_numbers(self): self.assertEqual(evaluate(["3 4 -"]), [-1]) @@ -55,6 +58,9 @@ def test_subtraction_errors_if_there_is_only_one_value_on_the_stack(self): str(err.exception.args[0]), "Insufficient number of items in stack" ) + def test_subtraction_more_than_two_values_on_the_stack(self): + self.assertEqual(evaluate(["1 12 3 -"]), [1, 9]) + def test_multiplication_can_multiply_two_numbers(self): self.assertEqual(evaluate(["2 4 *"]), [8]) @@ -74,6 +80,9 @@ def test_multiplication_errors_if_there_is_only_one_value_on_the_stack(self): str(err.exception.args[0]), "Insufficient number of items in stack" ) + def test_multiplication_more_than_two_values_on_the_stack(self): + self.assertEqual(evaluate(["1 2 3 *"]), [1, 6]) + def test_division_can_divide_two_numbers(self): self.assertEqual(evaluate(["12 3 /"]), [4]) @@ -103,12 +112,21 @@ def test_division_errors_if_there_is_only_one_value_on_the_stack(self): str(err.exception.args[0]), "Insufficient number of items in stack" ) + def test_division_more_than_two_values_on_the_stack(self): + self.assertEqual(evaluate(["1 12 3 /"]), [1, 4]) + def test_combined_arithmetic_addition_and_subtraction(self): self.assertEqual(evaluate(["1 2 + 4 -"]), [-1]) def test_combined_arithmetic_multiplication_and_division(self): self.assertEqual(evaluate(["2 4 * 3 /"]), [2]) + def test_combined_arithmetic_multiplication_and_addition(self): + self.assertEqual(evaluate(["1 3 4 * +"]), [13]) + + def test_combined_arithmetic_addition_and_multiplication(self): + self.assertEqual(evaluate(["1 3 4 + *"]), [7]) + def test_dup_copies_a_value_on_the_stack(self): self.assertEqual(evaluate(["1 dup"]), [1, 1]) From 27931ea05d893fdb125264602701a658a049770a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 6 Nov 2024 14:35:18 -0800 Subject: [PATCH 744/826] Spellchecking in ref floder. Hopefully for the only time. (#3816) --- reference/concepts/boolean_values.md | 2 +- reference/concepts/builtin_types/dict.md | 2 +- reference/concepts/builtin_types/frozenset.md | 2 +- reference/concepts/builtin_types/list.md | 2 +- reference/concepts/constructor.md | 2 +- reference/concepts/default_arguments.md | 2 +- reference/concepts/dunder_methods.md | 2 +- reference/concepts/initialization.md | 2 +- reference/concepts/instance_attributes.md | 2 +- reference/concepts/instance_methods.md | 2 +- reference/concepts/instance_properties.md | 2 +- reference/concepts/regular_expressions.md | 2 +- reference/concepts/return_value.md | 4 ++-- reference/concepts/slicing.md | 2 +- reference/concepts/string_splitting.md | 2 +- reference/concepts/string_translation.md | 2 +- reference/concepts/type_hinting.md | 2 +- reference/exercise-concepts/binary-search-tree.md | 4 ++-- reference/exercise-concepts/leap.md | 2 +- reference/exercise-concepts/markdown.md | 6 +++--- reference/exercise-concepts/matrix.md | 12 ++++++------ reference/exercise-concepts/phone-number.md | 2 +- reference/exercise-concepts/reverse-string.md | 4 ++-- reference/exercise-concepts/rna-transcription.md | 4 ++-- reference/exercise-concepts/robot-simulator.md | 10 +++++----- 25 files changed, 40 insertions(+), 40 deletions(-) diff --git a/reference/concepts/boolean_values.md b/reference/concepts/boolean_values.md index 40bce78e7c..a3ec6c0b28 100644 --- a/reference/concepts/boolean_values.md +++ b/reference/concepts/boolean_values.md @@ -3,4 +3,4 @@ TODO: ADD MORE - this solution uses Boolean values (`True` / `False`) [hamming](../exercise-concepts/hamming.md) -- True and False of type `bopl`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. [markdown](../exercise-concepts/markdown.md) +- True and False of type `bool`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. [markdown](../exercise-concepts/markdown.md) diff --git a/reference/concepts/builtin_types/dict.md b/reference/concepts/builtin_types/dict.md index 90e7ec62d5..f636cb27c2 100644 --- a/reference/concepts/builtin_types/dict.md +++ b/reference/concepts/builtin_types/dict.md @@ -1,6 +1,6 @@ # `dict` -Python's primary [mapping type][docs-mapping-type] that associatess keys with values in a [hash map][hash-map]. +Python's primary [mapping type][docs-mapping-type] that associates keys with values in a [hash map][hash-map]. See examples of usage in [markdown][markdown], [rna-transcription][rna-transcription], and [robot-simulator][robot-simulator]. diff --git a/reference/concepts/builtin_types/frozenset.md b/reference/concepts/builtin_types/frozenset.md index 03d582e383..021a657eaa 100644 --- a/reference/concepts/builtin_types/frozenset.md +++ b/reference/concepts/builtin_types/frozenset.md @@ -2,7 +2,7 @@ TODO: ADD MORE DETAIL -See the Python documentation entries for the [`set`][docs-set] collection, the [immutable][set type][docs-set-type]; essentially a [hash map][hash-map] in which only the key is relevant, and which disallows [mutaion][mutation] of keys after intialization. +See the Python documentation entries for the [`set`][docs-set] collection, the [immutable][set type][docs-set-type]; essentially a [hash map][hash-map] in which only the key is relevant, and which disallows [mutation][mutation] of keys after initialization. [immutable]: https://github.com/exercism/v3/blob/main/reference/concepts/immutability.md [mutation]: https://github.com/exercism/v3/blob/main/reference/concepts/mutation.md diff --git a/reference/concepts/builtin_types/list.md b/reference/concepts/builtin_types/list.md index 364df7b9f5..c7d3231a0f 100644 --- a/reference/concepts/builtin_types/list.md +++ b/reference/concepts/builtin_types/list.md @@ -8,7 +8,7 @@ A multi-dimensional list-with-a-list is used as a simple (but not very efficient TODO: ADD MORE DETAIL -See the Python documentation entries for the [mutable][mutation] [`list` type][docs-list-type] and it's [constructor][list-as-function]. This is Python's most commonly used [sequential collection][docs-sequence-types], and as it allows _heterogenous data_ it's quite different from the [fixed array][general-concept-array] and [singly-linked list][general-concept-list] types you may have encountered in other, less flexible, languages. +See the Python documentation entries for the [mutable][mutation] [`list` type][docs-list-type] and it's [constructor][list-as-function]. This is Python's most commonly used [sequential collection][docs-sequence-types], and as it allows _heterogeneous data_ it's quite different from the [fixed array][general-concept-array] and [singly-linked list][general-concept-list] types you may have encountered in other, less flexible, languages. [variable-length-quantity]: ../../exercise-concepts/variable-length-quantity.md [markdown]: ../../exercise-concepts/markdown.md diff --git a/reference/concepts/constructor.md b/reference/concepts/constructor.md index 292e436fa1..b3d14c459c 100644 --- a/reference/concepts/constructor.md +++ b/reference/concepts/constructor.md @@ -3,4 +3,4 @@ TODO: ADD MORE - student needs to know how to build an object using its constructor [binary-search-tree](../exercise-concepts/binary-search-tree.md) -- customizing object initalization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property [matrix](../exercise-concepts/matrix.md) +- customizing object initialization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property [matrix](../exercise-concepts/matrix.md) diff --git a/reference/concepts/default_arguments.md b/reference/concepts/default_arguments.md index 987eaee82f..0131934be4 100644 --- a/reference/concepts/default_arguments.md +++ b/reference/concepts/default_arguments.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initally passed. [robot-simulator](../exercise-concepts/robot-simulator.md) +- pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initially passed. [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/dunder_methods.md b/reference/concepts/dunder_methods.md index 751eebea35..c52459ac4c 100644 --- a/reference/concepts/dunder_methods.md +++ b/reference/concepts/dunder_methods.md @@ -7,4 +7,4 @@ TODO: ADD MORE - "dunder" -> "double under", referring to the names of these methods being prefixed with two underscores, e.g. `__init__`. There is no formal privacy in Python, but conventionally a single underscore indicates a private method, or one that the programmer should assume may change at any time; methods without an underscore are considered part of an object's public API. Double underscores are even more special - they are used by Python's builtin functions like `len()`, for example, to allow objects to implement various interfaces and functionality. They can also be used for operator overloading. If you have a custom class that you would like to be able to compare to other instances of the same class, implementing `__lt__`, `__gt__`, `__eq__` etc. allow programmers to use the `>`, `<`, `=` operators. Dunder methods allow programmers to build useful objects with simple interfaces, i.e. you can add two instances together using `+` instead of writing something like `instance1.add(instance2)`. [hamming](../exercise-concepts/hamming.md) - the example uses the `__init__` magic method as its constructor for the class [matrix](../exercise-concepts/matrix.md) - User defined classes can (and generally do) overload the `__init__` method, whose first argument is `self`, because the result of `__init__` is a class _instance_. [phone-number](../exercise-concepts/phone-number.md) -- The example uses `__init__` as a constructor for the class, which also calls `__new__`. In addition, the example uses `__call__()` via the appending of `()` to instance method names, and `__eq__()` (_rich compairison_) via the use of `==` [robot-simulator](../exercise-concepts/robot-simulator.md) +- The example uses `__init__` as a constructor for the class, which also calls `__new__`. In addition, the example uses `__call__()` via the appending of `()` to instance method names, and `__eq__()` (_rich_comparison_) via the use of `==` [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/initialization.md b/reference/concepts/initialization.md index 20979b9926..c0880200d5 100644 --- a/reference/concepts/initialization.md +++ b/reference/concepts/initialization.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- customizing object instatiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. [robot-simulator](../exercise-concepts/robot-simulator.md) +- customizing object instantiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/instance_attributes.md b/reference/concepts/instance_attributes.md index 1ee02d76d9..7251ef962f 100644 --- a/reference/concepts/instance_attributes.md +++ b/reference/concepts/instance_attributes.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- this exercise rquires one or more instance attributes to persist passed in data. [robot-simulator](../exercise-concepts/robot-simulator.md) +- this exercise requires one or more instance attributes to persist passed in data. [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/instance_methods.md b/reference/concepts/instance_methods.md index 5b61f32c57..25ec8caa1c 100644 --- a/reference/concepts/instance_methods.md +++ b/reference/concepts/instance_methods.md @@ -4,6 +4,6 @@ TODO: ADD MORE - the exercise relies on the `def` statement to create an instance method [allergies](../exercise-concepts/allergies.md) - use of `def` to define a class's methods [clock](../exercise-concepts/clock.md) -- classes can have instance _methods_ which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid uneeded computation [phone-number](../exercise-concepts/phone-number.md) +- classes can have instance _methods_ which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid unneeded computation [phone-number](../exercise-concepts/phone-number.md) - tests for this exercises require one or more instance methods that will return a specified row or column list of the `matrix`. [matrix](../exercise-concepts/matrix.md) - tests for this exercises require one or more instance methods that will take in a set of starting coordinates and a bearing and then accept a series of instructions that "move" the instance to a new set of coordinates and bearing. [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/instance_properties.md b/reference/concepts/instance_properties.md index f4a05ee9c9..39c49a98e2 100644 --- a/reference/concepts/instance_properties.md +++ b/reference/concepts/instance_properties.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- this exercise rquires one or more instance properties to persist passed in data. [matrix](../exercise-concepts/matrix.md) +- this exercise requires one or more instance properties to persist passed in data. [matrix](../exercise-concepts/matrix.md) diff --git a/reference/concepts/regular_expressions.md b/reference/concepts/regular_expressions.md index 821210074c..8721003a24 100644 --- a/reference/concepts/regular_expressions.md +++ b/reference/concepts/regular_expressions.md @@ -4,7 +4,7 @@ TODO: ADD MORE - the `re.sub()` function of the `re` module that replaces a `regular expression` match with a new value. The example solutions use this function in various places to substitute _markdown_ syntax for _HTML_ syntax in the passed in markdown text. [markdown](../exercise-concepts/markdown.md) - Both the original code to be refactored for this exercise and the example solution import and use the `re` module for Regular Expressions in python. [markdown](../exercise-concepts/markdown.md) -- the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compliled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or subsitituting. [markdown](../exercise-concepts/markdown.md) +- the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compiled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or substituting. [markdown](../exercise-concepts/markdown.md) - Various functions in the re module return a `re.Match` _instance_ which in turn has a `Match.group` method. `Match.group` exists even if there are no groups specified in the pattern. See the [Match.group docs](https://docs.python.org/3/library/re.html#re.Match.group) for more detail. [markdown](../exercise-concepts/markdown.md) - regular expressions is a language of sorts that can detect substrings and extract groups from a string, as well as replace them with something else [phone-number](../exercise-concepts/phone-number.md) - A Domain Specific Language (DSL) for text processing. Like many other programming languages in use, python supports a quasi-dialect of PCRE (_Perl compatible regular expressions_). `Regular expressions` can be used via the core python `re` module, or the third-party `regex` module. Both the original code to be refactored for this exercise and the example solutions use the core `re` module to access `regular expressions` functionality. [markdown](../exercise-concepts/markdown.md) diff --git a/reference/concepts/return_value.md b/reference/concepts/return_value.md index 5cd84d303e..e2074391c5 100644 --- a/reference/concepts/return_value.md +++ b/reference/concepts/return_value.md @@ -7,7 +7,7 @@ TODO: ADD MORE - Most of the functions in the example solution specify a _return_ value using the `return` keyword. [markdown](../exercise-concepts/markdown.md) - the exercise must use a `return` statement to return a value to the caller [leap](../exercise-concepts/leap.md) - this function return a string by this line: `return text[::-1]` [reverse-string](../exercise-concepts/reverse-string.md) -- the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an expicit `return` keyword and statment will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` [rna-transcription](../exercise-concepts/rna-transcription.md) -- knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all of the functions in the example omit an explicit `return` statment and all return `None`. [robot-simulator](../exercise-concepts/robot-simulator.md) +- the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an explicit `return` keyword and statement will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` [rna-transcription](../exercise-concepts/rna-transcription.md) +- knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all functions in the example omit an explicit `return` statement and all return `None`. [robot-simulator](../exercise-concepts/robot-simulator.md) - the knowledge of `return` statement could be a useful concept in this exercise [variable-length-quantity](../exercise-concepts/variable-length-quantity.md) - "row" and "column" list values are expected from defined instance method(s) [matrix](../exercise-concepts/matrix.md) diff --git a/reference/concepts/slicing.md b/reference/concepts/slicing.md index dbfe491561..2694063c0f 100644 --- a/reference/concepts/slicing.md +++ b/reference/concepts/slicing.md @@ -4,4 +4,4 @@ TODO: ADD MORE - the extended solution to this exercise can employ a slice (returns a copy) instead of calling `.copy()`. [matrix](../exercise-concepts/matrix.md) - a slice within an iterable, i.e. the slice of items from `[x]` to `[y]`, can be accessed via `[x:y]` notation; a third parameter allows "skipping" by `z`, i.e. `stringname[x:y:z]` [phone-number](../exercise-concepts/phone-number.md) -- becase `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: [reverse-string](../exercise-concepts/reverse-string.md) +- because `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: [reverse-string](../exercise-concepts/reverse-string.md) diff --git a/reference/concepts/string_splitting.md b/reference/concepts/string_splitting.md index 778ca9ad7e..bb6f502c16 100644 --- a/reference/concepts/string_splitting.md +++ b/reference/concepts/string_splitting.md @@ -3,4 +3,4 @@ TODO: ADD MORE - The example solution uses `str.split()` to break the passed in markdown string into a list of lines broken up by the `\n` character. The alternate Python example solution uses `str.splitlines()` for the same effect across all line end characters. [markdown](../exercise-concepts/markdown.md) -- the example uses `str.split` with and without seperators to break the passed in string into "rows" and then "elements" [matrix](../exercise-concepts/matrix.md) +- the example uses `str.split` with and without separators to break the passed in string into "rows" and then "elements" [matrix](../exercise-concepts/matrix.md) diff --git a/reference/concepts/string_translation.md b/reference/concepts/string_translation.md index 2b0c5e9fdc..456b21abde 100644 --- a/reference/concepts/string_translation.md +++ b/reference/concepts/string_translation.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the inital string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. [rna-transcription](../exercise-concepts/rna-transcription.md) +- the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the initial string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. [rna-transcription](../exercise-concepts/rna-transcription.md) diff --git a/reference/concepts/type_hinting.md b/reference/concepts/type_hinting.md index cc18920d4e..18cf3b11a6 100644 --- a/reference/concepts/type_hinting.md +++ b/reference/concepts/type_hinting.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not neccessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. [reverse-string](../exercise-concepts/reverse-string.md) +- In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not necessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. [reverse-string](../exercise-concepts/reverse-string.md) diff --git a/reference/exercise-concepts/binary-search-tree.md b/reference/exercise-concepts/binary-search-tree.md index a44e11c99d..ae047fc9d5 100644 --- a/reference/exercise-concepts/binary-search-tree.md +++ b/reference/exercise-concepts/binary-search-tree.md @@ -60,7 +60,7 @@ class BinarySearchTree: ## Concepts -- [class][class]: a general comprehension of class concept and and how it works is required, `class` statement +- [class][class]: a general comprehension of class concept and how it works is required, `class` statement - [Implied Argument][implied-argument]: student needs to know how to use statement `self` in a class - [class members][class-members]: student must know how members of a class work - [class methods][class-methods]: student must know how methods of a class work inside and outside the class, the use and meaning of `def` statement @@ -78,5 +78,5 @@ class BinarySearchTree: - [Integer comparison][integer-comparison]: concept required to solve the exercise - [Recursion][recursion]: recursion is a core concept in this exercise - [Lists][lists]: knowledge of lists and iteration on lists is required for this exercise -- [Conditional structures][conditional-structures]: knowledge of conditional conceptis and `if...else` statements are required +- [Conditional structures][conditional-structures]: knowledge of conditional concepts and `if...else` statements are required - [Methods of list][methods-of-list]: the use of methods of list could be useful in this exercise. Methods like `append`, `pop`... diff --git a/reference/exercise-concepts/leap.md b/reference/exercise-concepts/leap.md index deb14475f9..6846d3b097 100644 --- a/reference/exercise-concepts/leap.md +++ b/reference/exercise-concepts/leap.md @@ -18,7 +18,7 @@ def leap(year): - [Modular Division][modular-division]: the exercise relies on the `%` operator to check if one number is evenly divisible by another - [Boolean Operators][boolean-operators]: the exercise relies on `and`, `or`, and (optionally) `not` to form Boolean predicates - [Boolean Logic][boolean-logic]: the exercise relies on `and` and `or` to combine Boolean predicates into a single logical answer -- [Comparision][comparision]: the exercise relies on the `==` and `!=` operators to make binary comparisons between values +- [Comparison][comparison]: the exercise relies on the `==` and `!=` operators to make binary comparisons between values - [Equivalence][equivalence]: the exercise relies on the `==` and `!=` operators to check that two values are equivalent (or not) - [Order of Evaluation][order-of-evaluation]: the exercise relies on parentheses to explicitly modify the normal order of evaluation of an expression - [Operator Precedence][operator-precedence]: the exercise is most simply stated when the student understands the operator precedence binding rules of Python diff --git a/reference/exercise-concepts/markdown.md b/reference/exercise-concepts/markdown.md index 34c896b883..137f1a8179 100644 --- a/reference/exercise-concepts/markdown.md +++ b/reference/exercise-concepts/markdown.md @@ -161,14 +161,14 @@ def parse(markdown: str) -> str: - [Regular Expressions][regular-expressions]: Both the original code to be refactored for this exercise and the example solution import and use the `re` module for Regular Expressions in python. - [Importing][importing]: Both the original code to be refactored for the exercise and the example solution use the `import` keyword to import the `re` module in support of Regular Expressions in python. - [String Splitting][string-splitting]: The example solution uses `str.split()` to break the passed in markdown string into a list of lines broken up by the `\n` character. The alternate Python example solution uses `str.splitlines()` for the same effect across all line end characters. -- [Regular Expressions][regular-expressions]: the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compliled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or subsitituting. +- [Regular Expressions][regular-expressions]: the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compiled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or substituting. - [Regular expressions][regular-expressions]: A Domain Specific Language (DSL) for text processing. Like many other programming languages in use, python supports a quasi-dialect of PCRE (_Perl compatible regular expressions_). `Regular expressions` can be used via the core python `re` module, or the third-party `regex` module. Both the original code to be refactored for this exercise and the example solutions use the core `re` module to access `regular expressions` functionality. - [Return value][return-value]: Most of the functions in the example solution specify a _return_ value using the `return` keyword. - [None][none]: Pythons null type, referred to when a null or "placeholder" is needed. It is in and of itself a singleton in any given python program. -- [Booleans][booleans]: True and False of type `bopl`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. +- [Booleans][booleans]: True and False of type `bool`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. - [Assignment][assignment]: The example solution uses assignment for variables and other values. - [Regular Expressions][regular-expression]: the `re.sub()` function of the `re` module that replaces a `regular expression` match with a new value. The example solutions use this function in various places to substitute _markdown_ syntax for _HTML_ syntax in the passed in markdown text. -- [Dictionaries][dictionaries]: Mapping type. The example solution employes a dictionary to return values from the `parse_line()` function. +- [Dictionaries][dictionaries]: Mapping type. The example solution employs a dictionary to return values from the `parse_line()` function. - [For loops][for-loops]: The example solution uses `for` loops to iterate over various function inputs. - [Iteration][iterable]: The example solution uses the `for _ in _` syntax to iterate over a list of lines. This is possible because a list is an `iterable`. - [Conditionals][conditionals]: The example solution uses `if` to check for pattern matching and membership conditions in different functions for processing different markdown patterns. diff --git a/reference/exercise-concepts/matrix.md b/reference/exercise-concepts/matrix.md index 1b5b2da6c9..37fe76175d 100644 --- a/reference/exercise-concepts/matrix.md +++ b/reference/exercise-concepts/matrix.md @@ -53,17 +53,17 @@ class Matrix(object): - [Classes][classes]: the exercise objective is to define a `matrix` type. Tested methods are linked to a `matrix` class - [Objects][objects]: creating different instances with different data representing different `matrices` is tested -- [Constructor][constructor]: customizing object initalization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property +- [Constructor][constructor]: customizing object initialization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property - [Dunder Methods][dunder-methods]: the example uses the `__init__` magic method as its constructor for the class - [Return Values][return-value]: "row" and "column" list values are expected from defined instance method(s) - [Implicit Argument][implicit-argument]: the example uses the `self` implicit argument for methods and properties linked to a specific instance of the class - [Namespaces][namespaces]: knowing to use `self`.`` for instance properties and `self` as first argument to instance methods in a class - [Instance Methods][instance-methods]: tests for this exercises require one or more instance methods that will return a specified row or column list of the `matrix`. -- [Instance Properties][instance-properties]: this exercise rquires one or more instance properties to persist passed in data. +- [Instance Properties][instance-properties]: this exercise requires one or more instance properties to persist passed in data. - [Mutability][mutability]: in the extended example, knowing there are no protected or private properties in python and adjusting coding patterns - [Assignment][assignment]: instance properties need to be assigned passed in data - [Method Arguments][method-arguments]: the methods returning "row" and "column" need to take both `self` and an integer as arguments -- [Lists][lists]: this exercise requires "row" or "column" be returnd as a `list`. A `list` of `lists` is also the reccommended way to process and store the passed-in data. +- [Lists][lists]: this exercise requires "row" or "column" be returned as a `list`. A `list` of `lists` is also the recommended way to process and store the passed-in data. - [Indexing][indexing]: the "rows" and "columns" of this exercise need to be retrieved from a list of lists via index - [Bracket Notation][bracket-notation]: knowing that `[]` should be used to refer to a value at a specific index in a list - [Slicing][slicing]: the extended solution to this exercise can employ a slice (returns a copy) instead of calling `.copy()`. @@ -71,9 +71,9 @@ class Matrix(object): - [Iterables][iterables]: understanding that strings, lists, and other data structures can be iterated over in the same fashion - [Iterators][iterators]: the example solution for this exercise uses `zip()`, which returns an _iterator_. - [For Loop][for-loop]: iterating over the passed in `matrix` string using a `for` loop to extract "rows" and "columns" that are appended to a list -- [Comprehension Syntax][comprehension-syntax]: knowing that this is equivelent to a `for loop` - putting the row or column creation code _inside_ the list literal instead of using loop + append. -- [Zip][zip]: the example solution for this exercise uses this function to aggregage the column-wise elements of each rown list to form the matrix "columns". +- [Comprehension Syntax][comprehension-syntax]: knowing that this is equivalent to a `for loop` - putting the row or column creation code _inside_ the list literal instead of using loop + append. +- [Zip][zip]: the example solution for this exercise uses this function to aggregate the column-wise elements of each row list to form the matrix "columns". - [Argument Unpacking][argument unpacking]: the example solution for this exercise uses `splat` (`*`) to unpack rows for the `zip()` function. -- [String Splitting][string-splitting]: the example uses `str.split` with and without seperators to break the passed in string into "rows" and then "elements" +- [String Splitting][string-splitting]: the example uses `str.split` with and without separators to break the passed in string into "rows" and then "elements" - [Type Conversion][type-conversion]: the passed in data is in `str` format but the output is expected as a list of type `int`. - [Int][int]: the example converts the parsed `str` elements into `int` diff --git a/reference/exercise-concepts/phone-number.md b/reference/exercise-concepts/phone-number.md index b7b631f4ab..d56201ccdc 100644 --- a/reference/exercise-concepts/phone-number.md +++ b/reference/exercise-concepts/phone-number.md @@ -40,7 +40,7 @@ class PhoneNumber: - [Class][class]: classes are defined with the `class :` syntax - [Dunder Methods][dunder-methods]: User defined classes can (and generally do) overload the `__init__` method, whose first argument is `self`, because the result of `__init__` is a class _instance_. - [Inheritance][inheritance]: The default `__str___` method is inherited from `Object`, which every class in Python inherits from. (See: inheritance) -- [Methods][methods]: classes can have instance _methods_ which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid uneeded computation +- [Methods][methods]: classes can have instance _methods_ which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid unneeded computation - [Non-Public Methods][non-public-methods]: Methods or attributes (including those of an imported module) prefixed with an underscore, `_`, are conventionally treated as "non-public" methods. Python does not support data privacy in the way a language like Java does. Instead convention dictates that methods and attributes that are not prefixed with a single underscore can be expected to remain stable along with semver, i.e. a public method will be backwards compatible with minor version updates, and can change with major version updates. Generally, importing non-public functions or using non-public methods is discouraged, though Python will not explicitly stop the programmer from doing so. - [Implied Argument][implied-argument]: within the class definition, methods and properties can be accessed via the `self.` notation - [Inheritance][inheritance]: a "subclass" will inherit all methods, attributes from it's parent class, and can then override methods as needed. Overriding means the logic in the parent class is not used. The `super` builtin function (not shown here) exists to allow the programmer to defer logic up the inheritance chain to the parent class when needed. diff --git a/reference/exercise-concepts/reverse-string.md b/reference/exercise-concepts/reverse-string.md index 52b5fd4e2f..b9a9944e9e 100644 --- a/reference/exercise-concepts/reverse-string.md +++ b/reference/exercise-concepts/reverse-string.md @@ -16,7 +16,7 @@ def reverse(text: str = "") -> str: - [Immutability][immutability]: `text` str in Python is [immutable](https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str). In this exercise, you return a new string, the old string `text` is not changed. - [Return Value][return-value]: this function return a string by this line: `return text[::-1]` -- [Slicing][slicing]: becase `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: +- [Slicing][slicing]: because `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: - `start`: 0-index of the start position, `start=0` by default (i.e., not specified) (start from the beginning) - `stop`: 0-index of the stop position, `stop=-1` by default (i.e., not specified) (stop at the end) @@ -31,4 +31,4 @@ def reverse(text: str = "") -> str: [Extra material for string slicing.](https://www.digitalocean.com/community/tutorials/how-to-index-and-slice-strings-in-python-3) - [Docstrings][docstrings]: used to document the function, normally situated right below `def func():` -- [Type hinting][type-hinting]: In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not neccessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. +- [Type hinting][type-hinting]: In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not necessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. diff --git a/reference/exercise-concepts/rna-transcription.md b/reference/exercise-concepts/rna-transcription.md index 004ded9511..3eb5f63b6f 100644 --- a/reference/exercise-concepts/rna-transcription.md +++ b/reference/exercise-concepts/rna-transcription.md @@ -16,7 +16,7 @@ def to_rna(dna_strand): - [Static Methods][static-methods]: Distinct from built-in functions, instance methods, and class methods, these are methods that are bound to a class, rather than an instance, and called _without_ explicitly or implicitly passing in an object of the class. The example solution for this exercise uses the `static` `str` method `maketrans`. - [String Methods][string-methods]: this exercise uses `str.maketrans()` (a static method of `str` that returns a dictionary to create a _translation table_ as required by the `str.translate()` instance method. This method is unusual in that it takes either a single dictionary or two strings of equal length. The example solution for this exercise uses `str.maketrans()` with a two-string argument. - [Dictionary][dictionary]: mapping type that has key-value pairs. Returned by `str.maketrans` in the example code. Also one of the argument types accepted by `str.maketrans()`. -- [String Translation][string-translation]: the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the inital string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. +- [String Translation][string-translation]: the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the initial string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. - [Function][function]: A named (_and often reusable_) section of code that performs a specific task. It may or may not have _arguments_ passed in, and may or may not _return_ data. Created using the `def` keyword. - [Function Arguments][function-arguments]: Parameters passed into a function. In python, these are noted in the `()` following a function name. The example code uses a function named `to_rna()` with an argument of `dna_strand`. -- [Return Value][return-value]: the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an expicit `return` keyword and statment will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` +- [Return Value][return-value]: the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an explicit `return` keyword and statement will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` diff --git a/reference/exercise-concepts/robot-simulator.md b/reference/exercise-concepts/robot-simulator.md index 8f80f67c1c..702ada899a 100644 --- a/reference/exercise-concepts/robot-simulator.md +++ b/reference/exercise-concepts/robot-simulator.md @@ -67,8 +67,8 @@ class Robot: - [Range][range]: the `range()` built-in type represents an immutable sequence of numbers (or any object that implements the `__index__` dunder method). Used in the example to represent the values from zero to 3 as assigned to NORTH, EAST, SOUTH, WEST. - [Class][class]: the exercise objective is to define a `robot` type. Tested methods are linked to a `robot` class. - [Instantiation][instantiation]: creating different instances of the `robot` class with different data representing different starting positions and bearing are tested. -- [Initialization][initialization]: customizing object instatiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. -- [Return Value][return-value]: knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all of the functions in the example omit an explicit `return` statment and all return `None`. +- [Initialization][initialization]: customizing object instantiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. +- [Return Value][return-value]: knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all of the functions in the example omit an explicit `return` statement and all return `None`. - [Implicit Argument][implicit-argument]: the example uses `self` for methods and properties linked to a specific instance of the class. - [Namespaces][namespaces]: knowing to use `self.` for instance attributes and `self` as first argument to instance methods in a class. Additionally, the example uses `self.()` to call a previously stored method name. - [Instance Methods][instance-methods]: tests for this exercises require one or more instance methods that will take in a set of starting coordinates and a bearing and then accept a series of instructions that "move" the instance to a new set of coordinates and bearing. @@ -76,11 +76,11 @@ class Robot: - [Higher-Order Function][higher-order-function]: a function that takes one or more other functions as arguments, _returning_ a function as its return value. The example uses the built-in `property()` as a higher-order function through `@property`. - [Property][property]: the `property()` built-in is a function that returns a property attribute. When used as a decorator, this transforms the passed-in method into a _getter_ method for read-only attribute with the same name and docstring. - [Assignment][assignment]: the example uses assignment for all the instance properties and `instructions` dictionary. -- [Instance Attributes][instance-attributes]: this exercise rquires one or more instance attributes to persist passed in data. +- [Instance Attributes][instance-attributes]: this exercise requires one or more instance attributes to persist passed in data. - [Mutability][mutability]: in the example, knowing there are no protected or private properties in python and so consciously mutating `self.x`, `self.y` and `self.compass` through the called instance methods. - [Method Parameters][method-parameters]: the example `__init__` method has `self`, direction, x, and y (coordinates) as parameters. It also uses `self` and `commands` (a string) for parameters of the `move()` method. -- [Default Arguments][default-arguments]: pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initally passed. -- [Dictionary][dictionary]: the example uses a dictionary to map paassed in move arguments to methods that perform the moves. The example also uses a dictionary/mapping created by calling `str.maketrans()`. +- [Default Arguments][default-arguments]: pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initially passed. +- [Dictionary][dictionary]: the example uses a dictionary to map passed in move arguments to methods that perform the moves. The example also uses a dictionary/mapping created by calling `str.maketrans()`. - [Indexing][indexing]: finding a value by key in a dictionary using `[]` The example uses passed in move arguments as `keys` to look up corresponding `values` (_method names_) for moving the robot in the _instructions_ dictionary. - [Iteration][iteration]: the example uses a `for loop` to iterate through the letters of the passed-in `commands` string and looks up the corresponding values in a dictionary, so that the appropriate methods can be called to move the `robot`. - [Composition][composition]: adding functionality from a class by incorporating an instance of that class in a class you are creating. The example creates a `robot` by instantiating a `compass` and assigning it to the `self`.compass attribute of `robot`. From 102bfb78f28a3a0b5142b6199b1d6571df807487 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 6 Nov 2024 15:17:13 -0800 Subject: [PATCH 745/826] Hopefully, the last typos of this round. (#3817) --- concepts/bitwise-operators/about.md | 2 +- exercises/practice/bob/.approaches/introduction.md | 2 +- .../practice/nth-prime/.approaches/generator-fun/content.md | 6 ++++-- .../practice/roman-numerals/.approaches/introduction.md | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/concepts/bitwise-operators/about.md b/concepts/bitwise-operators/about.md index a4ddb509c1..a68e5378f1 100644 --- a/concepts/bitwise-operators/about.md +++ b/concepts/bitwise-operators/about.md @@ -117,7 +117,7 @@ This means that all bits are inverted and a number is _**interpreted as negative Positive numbers have an MSB of `0`. This representation has the advantage of only having one version of zero, so that the programmer doesn't have to manage `-0` and `+0`. -This way of representing negative and positive numbers adds a complication for Python: there are no finite-integer concepts like `int32` or `int64` internally in the core langauge. +This way of representing negative and positive numbers adds a complication for Python: there are no finite-integer concepts like `int32` or `int64` internally in the core language. In 'modern' Python, `int`s are of unlimited size (_limited only by hardware capacity_), and a negative or bit-inverted number has a (_theoretically_) infinite number of `1`'s to the left, just as a positive number has unlimited `0`'s. This makes it difficult to give a useful example of `bitwise not`: diff --git a/exercises/practice/bob/.approaches/introduction.md b/exercises/practice/bob/.approaches/introduction.md index 07d68d1a1e..b9a54b9f57 100644 --- a/exercises/practice/bob/.approaches/introduction.md +++ b/exercises/practice/bob/.approaches/introduction.md @@ -13,7 +13,7 @@ Regardless of the approach used, some things you could look out for include - Use the [`endswith`][endswith] method instead of checking the last character by index for `?`. -- Don't copy/paste the logic for determining a shout and for determing a question into determing a shouted question. +- Don't copy/paste the logic for determining a shout and for determining a question into determining a shouted question. Combine the two determinations instead of copying them. Not duplicating the code will keep the code [DRY][dry]. diff --git a/exercises/practice/nth-prime/.approaches/generator-fun/content.md b/exercises/practice/nth-prime/.approaches/generator-fun/content.md index 39290335a0..b7fc867ab7 100644 --- a/exercises/practice/nth-prime/.approaches/generator-fun/content.md +++ b/exercises/practice/nth-prime/.approaches/generator-fun/content.md @@ -19,11 +19,13 @@ Using a lambda expression, we `filter` out any numbers above two that are prime. Doesn't this result in an infinite loop? No - `filter` _also_ returns a generator object (which are [evaluated lazily][generator]), so while it's too will produce values infinitely if evaluated, it doesn't hang to program at the time of instantiation. -`itertools.islice` takes in a generator object and an end count, returning a generator object which _only evalutes until that end count_. +`itertools.islice` takes in a generator object and an end count, returning a generator object which _only evaluates until that end count_. The next line exhausts all the values in the generator except the end, and we finally return the last element. -We can utilize the `functools.cache` decorator for greater speeds at higher values of `number`, so we take it out. The added bonus is that a very long line of code is cleant up. +We can utilize the `functools.cache` decorator for greater speeds at higher values of `number`, so we take it out. +The added bonus is that a very long line of code is cleaned up. + ```python from itertools import islice, count diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md index 35f73e3ace..49060c2029 100644 --- a/exercises/practice/roman-numerals/.approaches/introduction.md +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -186,11 +186,11 @@ As the textbooks say, further analysis of this approach is left as an exercise f ## Which approach to use? In production, it would make sense to use the `roman` package. -It is debugged and supports Roman-to-Arabic conversions in addtion to the Arabic-to-Roman approaches discussed here. +It is debugged and supports Roman-to-Arabic conversions in addition to the Arabic-to-Roman approaches discussed here. Most submissions, like the `roman` package implementation, use some variant of [`loop-over-romans`][loop-over-romans]. -Using a [2-D lookup table][table-lookup] takes a bit more initialization, but then everthing can be done in a list comprehension instead of nested loops. +Using a [2-D lookup table][table-lookup] takes a bit more initialization, but then everything can be done in a list comprehension instead of nested loops. Python is relatively unusual in supporting both tuples-of-tuples and relatively fast list comprehensions, so the approach seems a good fit for this language. No performance article is currently included for this exercise. From da351aea14672f5ebaab2629b44fc89bf3c28031 Mon Sep 17 00:00:00 2001 From: JD DeLaune <102549713+jddelaune@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:41:04 -0600 Subject: [PATCH 746/826] Fixed broken link in grains performance doc (#3822) --- exercises/practice/grains/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/grains/.articles/performance/content.md b/exercises/practice/grains/.articles/performance/content.md index b115001831..7ab12a1478 100644 --- a/exercises/practice/grains/.articles/performance/content.md +++ b/exercises/practice/grains/.articles/performance/content.md @@ -37,7 +37,7 @@ pow square 64: 5.738279999932274e-07 Using `if number not in range(1, 65):` was over `125` nanoseconds longer than using `if number < 1 or number > 64:` for all approaches. [approaches]: https://exercism.org/tracks/python/exercises/grains/approaches -[approach-bit-shifting]: https://exercism.org/python/csharp/exercises/grains/approaches/bit-shifting +[approach-bit-shifting]: https://exercism.org/tracks/python/exercises/grains/approaches/bit-shifting [approach-pow]: https://exercism.org/tracks/python/exercises/grains/approaches/pow [approach-exponentiation]: https://exercism.org/tracks/python/exercises/grains/approaches/exponentiation [benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/grains/.articles/performance/code/Benchmark.py From 8dde6fd43646a1e071aca2d5a1a324b503dc47a6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 17 Nov 2024 22:29:03 -0800 Subject: [PATCH 747/826] Testing fix for no-important-files workflow. (#3824) --- .github/workflows/no-important-files-changed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 812e912966..cfcd6db4d4 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -17,7 +17,7 @@ permissions: jobs: check: - uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main + uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@fix-no-important-files with: repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} ref: ${{ github.head_ref }} From bba3d8010d767508050bd09833f0f6672dab7d92 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 19 Nov 2024 10:41:40 -0800 Subject: [PATCH 748/826] Changed list method tests to validate the queue. Also ajdusted instructions. (#3825) [no important files changed] While these are new tests, the majority of existing solutions should pass them, so no need to do a mass re-test. --- .../.docs/instructions.md | 2 + .../list_methods_test.py | 310 +++++++++++++----- 2 files changed, 222 insertions(+), 90 deletions(-) diff --git a/exercises/concept/chaitanas-colossal-coaster/.docs/instructions.md b/exercises/concept/chaitanas-colossal-coaster/.docs/instructions.md index 40280179c8..0a5bf25ff0 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.docs/instructions.md +++ b/exercises/concept/chaitanas-colossal-coaster/.docs/instructions.md @@ -12,6 +12,8 @@ There are two queues for this ride, each represented as a `list`: You have been asked to write some code to better manage the guests at the park. You need to implement the following functions as soon as possible before the guests (and your boss, Chaitana!) get cranky. + Make sure you read carefully. + Some tasks ask that you change or update the existing queue, while others ask you to make a copy of it. ## 1. Add me to the queue diff --git a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py index 5df2af327b..7a754b78e1 100644 --- a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py +++ b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py @@ -1,6 +1,7 @@ import unittest -import pytest from copy import deepcopy +import pytest + from list_methods import ( add_me_to_the_queue, @@ -16,108 +17,215 @@ class ListMethodsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue(self): - test_data = [(['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), - (['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich')] - result_data = [['RobotGuy', 'WW', 'HawkEye'], - ['Tony', 'Bruce', 'RichieRich']] + test_data = [ + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ] - for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + for variant, (params, expected) in enumerate(test_data, start=1): + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + express_queue, normal_queue, ticket_type, person_name = deepcopy(params) + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = add_me_to_the_queue(*params) + + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The function returned {actual_result},\n' + f' but the tests expected {expected} after {person_name} was added.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=1) + def test_add_me_to_the_queue_validate_queue(self): + test_data = [ + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ] + + for variant, (params, expected) in enumerate(test_data, start=1): # Deepcopy() is needed here because the task expects the input lists to be mutated. # That mutation wrecks havoc with the verification and error messaging. express_queue, normal_queue, ticket_type, person_name = deepcopy(params) + express, normal, ticket, name = params with self.subTest(f'variation #{variant}', - express_queue=express_queue, - normal_queue=normal_queue, - ticket_type=ticket_type, - person_name=person_name, - expected=expected): + express=express, normal=normal, + ticket=ticket, name=name, expected=expected): - actual_result = add_me_to_the_queue(*params) - error_message = (f'Called add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}. ' - f'The function returned {actual_result}, but ' - f'The tests expected {expected} when adding ' - f'{person_name} to the queue.') + actual_result = add_me_to_the_queue(express, normal, ticket, name) + + if type == 1: + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The queue == {express}, but the tests expected\n' + f'queue == {expected} after {person_name} was added.' + ) - self.assertListEqual(actual_result, expected, msg=error_message) + self.assertIs(actual_result, express, msg=error_message) + + if type == 0: + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The queue == {normal}, but the tests expected \n' + f'queue == {expected} after {person_name} was added.' + ) + + self.assertIs(actual_result, normal, msg=error_message) @pytest.mark.task(taskno=2) def test_find_my_friend(self): - test_data = [(['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), - (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), - (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Rocket')] + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Rocket'), + ] + result_data = (0,1,4) for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): with self.subTest(f'variation #{variant}', params=params, expected=expected): actual_result = find_my_friend(*params) - error_message = (f'Called find_my_friend{params}. ' - f'The function returned {actual_result}, but ' - f'The tests expected {expected} when looking for ' - f'{params[-1]} in the queue.') + error_message = ( + f'\nCalled find_my_friend{params}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when looking for\n' + f'{params[-1]} in the queue.' + ) self.assertIs(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_add_me_with_my_friends(self): - test_data = [(['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), - (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky')] + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky'), + ] - result_data = [['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], - ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], - ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky']] + result_data = [ + ['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky'], + ] for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): - # Deepcopy() is needed here because the task expects the input lists to be mutated. # That mutation wrecks havoc with the verification and error messaging. queue, index, person_name = deepcopy(params) - with self.subTest(f'variation #{variant}', - queue=queue, - index=index, - person_name=person_name, - expected=expected): + with self.subTest(f'variation #{variant}', params=params, expected=expected): actual_result = add_me_with_my_friends(*params) - error_message = (f'Called add_me_with_my_friends{queue, index, person_name}. ' - f'The function returned {actual_result}, but ' - f'The tests expected {expected} when adding ' - f'{person_name} to position {index} in the queue.') + error_message = ( + f'\nCalled add_me_with_my_friends{queue, index, person_name}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when adding\n' + f'{person_name} to position {index} in the queue.' + ) + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_add_me_with_my_friends_validate_queue(self): + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky'), + ] + + result_data = [ + ['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky'], + ] - self.assertListEqual(actual_result, expected, msg=error_message) + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + start_queue, add_index, person_name = deepcopy(params) + queue, _, _ = params + + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = add_me_with_my_friends(*params) + error_message = ( + f'\nCalled add_me_with_my_friends{start_queue, add_index, person_name}.\n' + f'The function returned {actual_result},\n' + 'but the original queue was unmodified. The tests expected the \n' + f'*original* queue to be modified by adding "{person_name}".' + ) + + self.assertIs(actual_result, queue, msg=error_message) @pytest.mark.task(taskno=4) def test_remove_the_mean_person(self): - test_data = [(['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), - (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Ultron'), - (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Ultron'), + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), + (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), + (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Steve'), + ] + result_data = [ + ['Natasha', 'Steve', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Wanda', 'Ultron'], + ['Ultron', 'Natasha', 'Wanda', 'Rocket'], ] - result_data = [['Natasha', 'Steve', 'Wanda', 'Rocket'], - ['Natasha', 'Steve', 'Wanda', 'Rocket'], - ['Natasha', 'Steve', 'Wanda', 'Rocket']] for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): # Deepcopy() is needed here because the task expects the input lists to be mutated. # That mutation wrecks havoc with the verification and error messaging. - queue, person_name = deepcopy(params) + start_queue, person_name = deepcopy(params) - with self.subTest(f'variation #{variant}', - queue=queue, - person_name=person_name, - expected=expected): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = remove_the_mean_person(*params) + error_message = ( + f'\nCalled remove_the_mean_person{start_queue, person_name}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when removing\n' + f'{person_name} from the queue.' + ) + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_remove_the_mean_person_validate_queue(self): + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), + (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), + (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Steve'), + ] + + result_data = [ + ['Natasha', 'Steve', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Wanda', 'Ultron'], + ['Ultron', 'Natasha', 'Wanda', 'Rocket'], + ] + + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + start_queue, person_name = deepcopy(params) + queue, _ = params + with self.subTest(f'variation #{variant}', params=params, expected=expected): actual_result = remove_the_mean_person(*params) - error_message = (f'Called remove_the_mean_person{queue, person_name}. ' - f'The function returned {actual_result}, but ' - f'The tests expected {expected} when removing ' - f'{person_name} from the queue.') + error_message = ( + f'\nCalled remove_the_mean_person{start_queue, person_name}.\n' + f'The function returned {actual_result}, queue == {queue}.\n' + f'But the tests expected queue == {expected} when removing\n' + f'{person_name}.' + ) + + self.assertIs(actual_result, queue, msg=error_message) - self.assertListEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=5) def test_how_many_namefellows(self): @@ -141,46 +249,68 @@ def test_how_many_namefellows(self): @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): test_data = [ - (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Rocket'), - (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], 'Ultron'), - (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], 'Natasha') + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket'], 'Ultron'), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron'], 'Natasha') ] - for variant, (input, expected) in enumerate(test_data, start=1): - with self.subTest(f'variation #{variant}', input=input, expected=expected): - actual_result = remove_the_last_person(input) + for variant, (queue, modified, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, modified=modified, expected=expected): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + unmodified_queue = deepcopy(queue) expected_result = expected + actual_result = remove_the_last_person(queue) + expected_queue = modified - error_message = (f'Called remove_the_last_person({input}).' - f'The function returned {actual_result}, but the tests expected {expected_result}.') + error_message = (f'\nCalled remove_the_last_person({unmodified_queue}).\n' + f'The function was expected to remove and return the name "{expected_result}" ' + f'and change the queue to {expected_queue},\n' + f'but the name "{actual_result}" was returned and the queue == {queue}.') - self.assertEqual(actual_result, expected_result, msg=error_message) + self.assertEqual((actual_result, queue), (expected_result, expected_queue), msg=error_message) - @pytest.mark.task(taskno=6) - def test_remove_the_last_person_validate_queue(self): - test_data = [ - (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha']), - (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket']), - (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron']) - ] - for variant, (input, modified) in enumerate(test_data, start=1): - with self.subTest(f'variation #{variant}', input=input, modified=modified): - unmodified = deepcopy(input) - actual_result = remove_the_last_person(input) - expected_queue = modified - error_message = (f'\nCalled remove_the_last_person({unmodified}).\n' - f'The function was expected to change the queue to {expected_queue},\n' - f'but the queue looks like {input} instead.') + @pytest.mark.task(taskno=7) + def test_sorted_names(self): + test_data =( + (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), + (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), + (['Gamora', 'Loki', 'Tony', 'Peggy', 'Okoye'], ['Gamora', 'Loki', 'Okoye', 'Peggy', 'Tony']), + ) + + for variant, (queue, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, expected=expected): + actual_result = sorted_names(queue) + expected_result = expected + + error_message = (f'\nCalled sorted_names({queue}).\n' + f'The function returned {actual_result}, but \n' + f'the tests expect {expected_result}.') - self.assertEqual(input, expected_queue, msg=error_message) + self.assertEqual(actual_result, expected_result, msg=error_message) @pytest.mark.task(taskno=7) - def test_sorted_names(self): - test_data = ['Steve', 'Ultron', 'Natasha', 'Rocket'] - expected = ['Natasha', 'Rocket', 'Steve', 'Ultron'] - actual_result = sorted_names(test_data) - error_message = (f'Called sorted_names({test_data}).' - f'The function returned {actual_result}, but the tests ' - f'expected {expected}.') - - self.assertListEqual(actual_result, expected, msg=error_message) + def test_sorted_names_validate_queue(self): + test_data = ( + (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), + (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), + (['Gamora', 'Loki', 'Tony', 'Peggy', 'Okoye'], ['Gamora', 'Loki', 'Okoye', 'Peggy', 'Tony']), + ) + + for variant, (queue, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, expected=expected): + + # Deepcopy() is needed here because the input lists might be mutated. + # That mutation wrecks havoc with the verification and error messaging. + original_queue = deepcopy(queue) + actual_result = sorted_names(queue) + expected_result = expected + + error_message = (f'\nCalled sorted_names({original_queue}).\n' + f'The function returned {actual_result}, \n' + f'with a queue == {queue}.\n' + f'The tests expect {expected_result}, \n' + f'with a queue == {original_queue}.') + + self.assertIsNot(actual_result, queue, msg=error_message) From be191488d58b022ece632ee7064716c4fd1d033c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 19 Nov 2024 10:42:05 -0800 Subject: [PATCH 749/826] Added some notes on using * on the left-hand side for unpacking. (#3826) --- concepts/unpacking-and-multiple-assignment/about.md | 12 +++++++++++- .../locomotive-engineer/.docs/introduction.md | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 787e2ef08e..a24e2c6d1a 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -222,12 +222,20 @@ This will pack all the values into a `list`/`tuple`. >>> combined_fruits ("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango") -# If the * operator is used on the left side of "=" the result is a list +# If the * operator is used on the left side of "=" the result is a list. +# Note the trailing comma. >>> *combined_fruits_too, = *fruits, *more_fruits >>> combined_fruits_too ['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango'] + +# A list literal can be used instead, but might not be as readable. +>>> [*combined_fruits_too] = *fruits, *more_fruits +>>> combined_fruits_too +['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango'] ``` +For more details on the use of `*` and `**`, check out [PEP 3132][pep-3132] and [PEP 448][pep-448]. + ### Packing a dictionary with `**` Packing a dictionary is done by using the `**` operator. @@ -370,6 +378,8 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [items]: https://docs.python.org/3/library/stdtypes.html#dict.items [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ +[pep-448]: https://peps.python.org/pep-0448/ +[pep-3132]: https://peps.python.org/pep-3132/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp [view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 798a334aeb..39ba5b4909 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -213,12 +213,16 @@ This will pack all the values into a `list`/`tuple`. >>> combined_fruits ("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango") -# If the * operator is used on the left side of "=" the result is a list +# If the * operator is used on the left side of "=" the result is a list. +# Note the trailing comma. >>> *combined_fruits_too, = *fruits, *more_fruits >>> combined_fruits_too ['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango'] ``` +For more background on using `*` on the left-hand side, see [PEP 3132][pep-3132]. + + ### Packing a dictionary with `**` Packing a dictionary is done by using the `**` operator. @@ -361,6 +365,7 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [items]: https://docs.python.org/3/library/stdtypes.html#dict.items [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ +[pep-3132]: https://peps.python.org/pep-3132/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp [view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views From 2779c2749f3bf58c44635442f30c9d416214243e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 22 Nov 2024 09:55:30 -0800 Subject: [PATCH 750/826] [Roman Numeral Approaches]: Fix Broken Link in Approaches Introduction (#3828) https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman --> https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-romans --- exercises/practice/roman-numerals/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md index 49060c2029..3358c23f40 100644 --- a/exercises/practice/roman-numerals/.approaches/introduction.md +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -200,6 +200,6 @@ The problem is inherently limited in scope by the design of Roman numerals, so a [if-else]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/if-else [table-lookup]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/table-lookup -[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman +[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-romans [recurse-match]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/recurse-match [roman-module]: https://github.com/zopefoundation/roman From c38e94b94091f253dd4c65a7e33045493d29d884 Mon Sep 17 00:00:00 2001 From: Andrew Pam Date: Mon, 25 Nov 2024 06:14:11 +1100 Subject: [PATCH 751/826] Update content.md (#3829) Add missing URI for "dictionary approach" --- exercises/practice/scrabble-score/.approaches/enum/content.md | 1 + .../practice/scrabble-score/.approaches/nested-tuple/content.md | 1 + 2 files changed, 2 insertions(+) diff --git a/exercises/practice/scrabble-score/.approaches/enum/content.md b/exercises/practice/scrabble-score/.approaches/enum/content.md index 5c2ad3a18a..f5845a5091 100644 --- a/exercises/practice/scrabble-score/.approaches/enum/content.md +++ b/exercises/practice/scrabble-score/.approaches/enum/content.md @@ -48,6 +48,7 @@ The `score` function uses the same [generator expression][generator-expression] Instead of looking up the value in a _dictionary_, it looks up the `InEnum` class member value. [classes]: https://docs.python.org/3/tutorial/classes.html +[dictionary-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/dictionary [enum]: https://docs.python.org/3/library/enum.html [generator-expression]: https://peps.python.org/pep-0289/ [int-enum]: https://docs.python.org/3/library/enum.html#enum.IntEnum diff --git a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md index 70dc860a0a..6bbd28a6bc 100644 --- a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md +++ b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md @@ -30,6 +30,7 @@ You can read more about unpacking in the [concept:python/unpacking-and-multiple- Then the code checks if the character is in the unpacked letters and if it is we return its score. +[dictionary-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/dictionary [generator-expression]: https://peps.python.org/pep-0289/ [for-loop]: https://realpython.com/python-for-loop/ [tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences From 01fb2575e8a75775d3e0f5cf198120953afa02f5 Mon Sep 17 00:00:00 2001 From: glaxxie <86179463+glaxxie@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:47:14 -0600 Subject: [PATCH 752/826] Update TESTS.md (#3830) * Update TESTS.md - Change placeholder brackets from `{}` to `<>` - Add a note explain placeholder value - Change pytest version number to reflect the latest one * Update TOOLS.md Small change to fit with the version in `INSTALLATION.md` * Update docs/TESTS.md Using exercism note block and clearer example Co-authored-by: BethanyG * Update docs/TOOLS.md Add more details about tooling Co-authored-by: BethanyG --------- Co-authored-by: BethanyG --- docs/TESTS.md | 45 +++++++++++++++++++++++++++++---------------- docs/TOOLS.md | 5 ++++- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 54a10b5782..5a38cf4c55 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -28,14 +28,14 @@ Otherwise, the `pytest` installation will be global. ```powershell PS C:\Users\foobar> py -m pip install pytest pytest-cache pytest-subtests pytest-pylint -Successfully installed pytest-7.2.2 ... +Successfully installed pytest-8.3.3 ... ``` #### Linux / MacOS ```bash $ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint -Successfully installed pytest-7.2.2 ... +Successfully installed pytest-8.3.3 ... ``` @@ -43,23 +43,36 @@ To check if installation was successful: ```bash $ python3 -m pytest --version -pytest 7.2.2 +pytest 8.3.3 ``` ## Running the tests -To run the tests, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +To run the tests, go to the folder where the exercise is stored using `cd` in your terminal (_replace `` below with your path_). ```bash -$ cd {exercise-folder-location} +$ cd ``` +
+ +~~~~exercism/note + `` or most things inside angle brackets denote a **_placeholder value_**. +A normal path or file name should be written _without_ any brackets. + + +For example: `/Users/janedoe/exercism/python/exercises/concept/chaitanas-colossal-coaster` (on *nix systems), `C:\Users\janedoe\exercism\python\exercises\practice\hello-world\` (on Windows), `myFolder` or `my_file.py`. +~~~~ + +
+ + The file you will want to run usually ends in `_test.py`. This file contains the tests for the exercise solution, and are the same tests that run on the website when a solution is uploaded. -Next, run the following command in your terminal, replacing `{exercise_test.py}` with the location/name of the test file: +Next, run the following command in your terminal, replacing `` with the location/name of the test file: ```bash -$ python3 -m pytest -o markers=task {exercise_test.py} +$ python3 -m pytest -o markers=task ==================== 7 passed in 0.08s ==================== ``` @@ -88,15 +101,15 @@ When tests fail, `pytest` prints the text of each failed test, along with the ex Below is an generic example of a failed test: ```bash -$(my_venv) python3 -m pytest -o markers=task {exercise_test.py} +$(my_venv) python3 -m pytest -o markers=task =================== FAILURES ==================== ______________ name_of_failed_test ______________ -# Test code inside of {exercise_test.py} that failed. +# Test code inside of that failed. ... E TypeOfError: ReturnedValue != ExpectedValue -exercise_test.py:{line_of_failed_test}: TypeOfError +exercise_test.py:: TypeOfError ============ short test summary info ============ FAILED exercise_test.py::ExerciseTest::name_of_failed_test ========== 1 failed, 2 passed in 0.13s ========== @@ -216,10 +229,10 @@ If you do not know where you have installed Python, run the following command in ```bash $ python3 -c "import os, sys; print(os.path.dirname(sys.executable))" -{python_directory} + ``` -The _returned_ directory is where your current active Python version is installed, in this section it is referred to as `{python_directory}`. +The _returned_ directory is where your current active Python version is installed, in this section it is referred to as ``. #### Windows @@ -232,7 +245,7 @@ Then find the `Path` variable in your _User variables_, select it, and click `Ed ![Selecting the path variable](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-EnvironmentVariables.png) -Then add a new line, as shown in the picture, replacing `{python_directory}` with your Python installation's directory: +Then add a new line, as shown in the picture, replacing `` with your Python installation's directory: ![Add python to path](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-AddPythonPath.png) @@ -240,17 +253,17 @@ Then add a new line, as shown in the picture, replacing `{python_directory}` wit The below should work for most Linux and MacOS flavors with a `bash` shell. Commands may vary by Linux distro, and whether a `fish` or `zsh` shell is used. -Replace `{python_directory}` with the output of `python3 -c "import os, sys; print(os.path.dirname(sys.executable))"` +Replace `` with the output of `python3 -c "import os, sys; print(os.path.dirname(sys.executable))"` ```bash -export PATH=”$PATH:{python_directory}}” +export PATH=”$PATH:” ``` [Code Quality: Tools and Best Practices]: https://realpython.com/python-code-quality/ [Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html [configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats [marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks -[pdb]: https://docs.python.org/3.9/library/pdb.html +[pdb]: https://docs.python.org/3.11/library/pdb.html [pip]: https://pip.pypa.io/en/stable/getting-started/ [psf-installer]: https://www.python.org/downloads/ [pylint]: https://pylint.pycqa.org/en/latest/user_guide/ diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 20ce04ded0..bacb8626aa 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -30,7 +30,10 @@ If you have an editor, IDE, tool, or plugin recommendation, we encourage you to Before you start exploring, make sure that you have a recent version of Python installed. -The Exercism platform currently supports `Python 3.7 - 3.11.2` (_exercises and tests_) and `Python 3.11.2` (_tooling_). +The Exercism web platform currently supports `Python 3.7 - 3.11.5` (_exercises and tests_) and `Python 3.11.5` (_tooling_). +Our online test runner currently uses `pytest 7.2.2` and `pytest-subtests 0.11.0`. +Our online analyzer uses `pylint 2.17.7`. +Using different versions of `Python`, `pytest`, or `pylint` locally might give you different results than the website. For more information, please refer to [Installing Python locally][Installing Python locally].
From db94cb030748e5de605f0e78688fdb8675e5a53b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 30 Nov 2024 11:37:26 -0800 Subject: [PATCH 753/826] [Darts] : Add Approaches (#3386) * Initial docs for darts approaches. * Initial rough draft of darts approaches. * Removing .articles for now. * Wrapped up approaches content details and removed performance info. * Cleaned up typos and changed toss to throw. --- .../.approaches/booleans-as-ints/content.md | 36 +++++ .../.approaches/booleans-as-ints/snippet.txt | 3 + .../practice/darts/.approaches/config.json | 50 ++++++ .../.approaches/dict-and-dict-get/content.md | 72 +++++++++ .../.approaches/dict-and-dict-get/snippet.txt | 5 + .../.approaches/dict-and-generator/content.md | 69 +++++++++ .../dict-and-generator/snippet.txt | 8 + .../.approaches/if-statements/content.md | 73 +++++++++ .../.approaches/if-statements/snippet.txt | 8 + .../darts/.approaches/introduction.md | 146 ++++++++++++++++++ .../darts/.approaches/match-case/content.md | 85 ++++++++++ .../darts/.approaches/match-case/snippet.txt | 8 + .../.approaches/tuple-and-loop/content.md | 55 +++++++ .../.approaches/tuple-and-loop/snippet.txt | 7 + 14 files changed, 625 insertions(+) create mode 100644 exercises/practice/darts/.approaches/booleans-as-ints/content.md create mode 100644 exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt create mode 100644 exercises/practice/darts/.approaches/config.json create mode 100644 exercises/practice/darts/.approaches/dict-and-dict-get/content.md create mode 100644 exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt create mode 100644 exercises/practice/darts/.approaches/dict-and-generator/content.md create mode 100644 exercises/practice/darts/.approaches/dict-and-generator/snippet.txt create mode 100644 exercises/practice/darts/.approaches/if-statements/content.md create mode 100644 exercises/practice/darts/.approaches/if-statements/snippet.txt create mode 100644 exercises/practice/darts/.approaches/introduction.md create mode 100644 exercises/practice/darts/.approaches/match-case/content.md create mode 100644 exercises/practice/darts/.approaches/match-case/snippet.txt create mode 100644 exercises/practice/darts/.approaches/tuple-and-loop/content.md create mode 100644 exercises/practice/darts/.approaches/tuple-and-loop/snippet.txt diff --git a/exercises/practice/darts/.approaches/booleans-as-ints/content.md b/exercises/practice/darts/.approaches/booleans-as-ints/content.md new file mode 100644 index 0000000000..d3c1541d2a --- /dev/null +++ b/exercises/practice/darts/.approaches/booleans-as-ints/content.md @@ -0,0 +1,36 @@ +# Using Boolean Values as Integers + + +```python +def score(x_coord, y_coord): + radius = (x_coord**2 + y_coord**2) + return (radius<=1)*5 + (radius<=25)*4 + (radius<=100)*1 +``` + + +In Python, the [Boolean values `True` and `False` are _subclasses_ of `int`][bools-as-ints] and can be interpreted as `0` (False) and `1` (True) in a mathematical context. +This approach leverages that interpretation by checking which areas the throw falls into and multiplying each Boolean `int` by a scoring multiple. +For example, a throw that lands on the 25 (_or 5 if using `math.sqrt(x**2 + y**2)`_) circle should have a score of 5: + +```python +>>> (False)*5 + (True)*4 + (True)*1 +5 +``` + + +This makes for very compact code and has the added boost of not requiring any `loops` or additional data structures. +However, it is considered bad form to rely on Boolean interpretation. +Instead, the Python documentation recommends an explicit conversion to `int`: + + +```python +def score(x_coord, y_coord): + radius = (x_coord**2 + y_coord**2) + return int(radius<=1)*5 + int(radius<=25)*4 + int(radius<=100)*1 +``` + +Beyond that recommendation, the terseness of this approach might be harder to reason about or decode — especially if a programmer is coming from a programming langauge that does not treat Boolean values as `ints`. +Despite the "radius" variable name, it is also more difficult to relate the scoring "rings" of the Dartboard to the values being checked and calculated in the `return` statement. +If using this code in a larger program, it would be strongly recommended that a docstring be provided to explain the Dartboard rings, scoring rules, and the corresponding scores. + +[bools-as-ints]: https://docs.python.org/3/library/stdtypes.html#boolean-type-bool \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt b/exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt new file mode 100644 index 0000000000..ec7dcfabbf --- /dev/null +++ b/exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt @@ -0,0 +1,3 @@ +def score(x_coord, y_coord): + radius = (x_coord**2 + y_coord**2) + return (radius<=1)*5 + (radius<=25)*4 +(radius<=100)*1 \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/config.json b/exercises/practice/darts/.approaches/config.json new file mode 100644 index 0000000000..77f331bfce --- /dev/null +++ b/exercises/practice/darts/.approaches/config.json @@ -0,0 +1,50 @@ +{ + "introduction": { + "authors": ["bethanyg"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "7d78f598-8b4c-4f7f-89e1-e8644e934a4c", + "slug": "if-statements", + "title": "Use If Statements", + "blurb": "Use if-statements to check scoring boundaries for a dart throw.", + "authors": ["bethanyg"] + }, + { + "uuid": "f8f5533a-09d2-4b7b-9dec-90f268bfc03b", + "slug": "tuple-and-loop", + "title": "Use a Tuple & Loop through Scores", + "blurb": "Score the Dart throw by looping through a tuple of scores.", + "authors": ["bethanyg"] + }, + { + "uuid": "a324f99e-15bb-43e0-9181-c1652094bc4f", + "slug": "match-case", + "title": "Use Structural Pattern Matching ('Match-Case')", + "blurb": "Use a Match-Case (Structural Pattern Matching) to score the dart throw.)", + "authors": ["bethanyg"] + }, + { + "uuid": "966bd2dd-c4fd-430b-ad77-3a304dedd82e", + "slug": "dict-and-generator", + "title": "Use a Dictionary with a Generator Expression", + "blurb": "Use a generator expression looping over a scoring dictionary, getting the max score for the dart throw.", + "authors": ["bethanyg"] + }, + { + "uuid": "5b087f50-31c5-4b84-9116-baafd3a30ed6", + "slug": "booleans-as-ints", + "title": "Use Boolean Values as Integers", + "blurb": "Use True and False as integer values to calculate the score of the dart throw.", + "authors": ["bethanyg"] + }, + { + "uuid": "0b2dbcd3-f0ac-45f7-af75-3451751fd21f", + "slug": "dict-and-dict-get", + "title": "Use a Dictionary with dict.get", + "blurb": "Loop over a dictionary and retrieve score via dct.get.", + "authors": ["bethanyg"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/dict-and-dict-get/content.md b/exercises/practice/darts/.approaches/dict-and-dict-get/content.md new file mode 100644 index 0000000000..a3c5bc2ac5 --- /dev/null +++ b/exercises/practice/darts/.approaches/dict-and-dict-get/content.md @@ -0,0 +1,72 @@ +# Using a Dictionary and `dict.get()` + + +```python +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = { + point <= 100: 1, + point <= 25: 5, + point <= 1: 10 + } + + return scores.get(True, 0) +``` + +At first glance, this approach looks similar to the [Booleans as Integers][approach-boolean-values-as-integers] approach, due to the Boolean evaluation used in the dictionary keys. +However, this approach is **not** interpreting Booleans as integers and is instead exploiting three key properties of [dictionaries][dicts]: + + +1. [Keys must be hashable][hashable-keys] — in other words, keys have to be _unique_. +2. Insertion order is preserved (_as of `Python 3.7`_), and evaluation/iteration happens in insertion order. +3. Duplicate keys _overwrite_ existing keys. + If the first key is `True` and the third key is `True`, the _value_ from the third key will overwrite the value from the first key. + +Finally, the `return` line uses [`dict.get()`][dict-get] to `return` a default value of 0 when a throw is outside the existing circle radii. +To see this in action, you can view this code on [Python Tutor][dict-get-python-tutor]. + + +Because of the listed dictionary qualities, **_order matters_**. +This approach depends on the outermost scoring circle containing all smaller circles and that +checks proceed from largest --> smallest circle. +Iterating in the opposite direction will not resolve to the correct score. +The following code variations do not pass the exercise tests: + + +```python + +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = { + point <= 1: 10, + point <= 25: 5, + point <= 100: 1, + } + + return scores.get(True, 0) + + #OR# + +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = { + point <= 25: 5, + point <= 1: 10, + point <= 100: 1, + } + + return scores.get(True, 0) + +``` + +While this approach is a _very clever_ use of dictionary properties, it is likely to be very hard to reason about for those who are not deeply knowledgeable. +Even those experienced in Python might take longer than usual to figure out what is happening in the code. +Extensibility could also be error-prone due to needing a strict order for the `dict` keys. + +This approach offers no space or speed advantages over using `if-statements` or other strategies, so is not recommended for use beyond a learning context. + +[approach-boolean-values-as-integers]: https://exercism.org/tracks/python/exercises/darts/approaches/boolean-values-as-integers +[dicts]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get +[dict-get-python-tutor]: https://pythontutor.com/render.html#code=def%20score%28x_coord,%20y_coord%29%3A%0A%20%20%20%20point%20%3D%20%28x_coord**2%20%2B%20y_coord**2%29%0A%20%20%20%20scores%20%3D%20%7B%0A%20%20%20%20%20%20%20%20point%20%3C%3D%20100%3A%201,%0A%20%20%20%20%20%20%20%20point%20%3C%3D%2025%3A%205,%0A%20%20%20%20%20%20%20%20point%20%3C%3D%201%3A%2010%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20return%20scores.get%28True,%200%29%0A%20%20%20%20%0Aprint%28score%281,3%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false +[hashable-keys]: https://www.pythonmorsels.com/what-are-hashable-objects/#dictionary-keys-must-be-hashable diff --git a/exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt b/exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt new file mode 100644 index 0000000000..6d496f54d3 --- /dev/null +++ b/exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt @@ -0,0 +1,5 @@ +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = {point <= 100: 1, point <= 25: 5, point <= 1: 10} + + return scores.get(True, 0) \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/dict-and-generator/content.md b/exercises/practice/darts/.approaches/dict-and-generator/content.md new file mode 100644 index 0000000000..30ffeac1eb --- /dev/null +++ b/exercises/practice/darts/.approaches/dict-and-generator/content.md @@ -0,0 +1,69 @@ +# Use a Dictionary and a Generator Expression + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1, 200: 0} + + return max(point for distance, point in + rules.items() if throw <= distance) +``` + + +This approach is very similar to the [tuple and loop][approach-tuple-and-loop] approach, but iterates over [`dict.items()`][dict-items] and writes the `loop` as a [`generator-expression`][generator-expression] inside `max()`. +In cases where the scoring circles overlap, `max()` will return the maximum score available for the throw. +The generator expression inside `max()` is the equivalent of using a `for-loop` and a variable to determine the max score: + + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1} + max_score = 0 + + for distance, point in rules.items(): + if throw <= distance and point > max_score: + max_score = point + return max_score +``` + + +A `list` or `tuple` can also be used in place of `max()`, but then requires an index to return the max score: + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1, 200: 0} + + return [point for distance, point in + rules.items() if throw <= distance][0] #<-- have to specify index 0. + +#OR# + +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1, 200: 0} + + return tuple(point for distance, point in + rules.items() if throw <= distance)[0] +``` + + +This solution can even be reduced to a "one-liner". +However, this is not performant, and is difficult to read: + +```python +def score(x_coord, y_coord): + return max(point for distance, point in + {1: 10, 25: 5, 100: 1, 200: 0}.items() if + (x_coord**2 + y_coord**2) <= distance) +``` + +While all of these variations do pass the tests, they suffer from even more over-engineering/performance caution than the earlier tuple and loop approach (_although for the data in this problem, the performance hit is slight_). +Additionally, the dictionary will take much more space in memory than using a `tuple` of tuples to hold scoring values. +In some circumstances, these variations might also be harder to reason about for those not familiar with `generator-expressions` or `list comprehensions`. + + +[approach-tuple-and-loop]: https://exercism.org/tracks/python/exercises/darts/approaches/tuple-and-loop +[dict-items]: https://docs.python.org/3/library/stdtypes.html#dict.items +[generator-expression]: https://dbader.org/blog/python-generator-expressions diff --git a/exercises/practice/darts/.approaches/dict-and-generator/snippet.txt b/exercises/practice/darts/.approaches/dict-and-generator/snippet.txt new file mode 100644 index 0000000000..f6649cf3a9 --- /dev/null +++ b/exercises/practice/darts/.approaches/dict-and-generator/snippet.txt @@ -0,0 +1,8 @@ +def score(x_coord, y_coord): + length = x_coord**2 + y_coord**2 + rules = {1.0: 10, 25.0: 5, 100.0: 1, 200: 0} + score = max(point for + distance, point in + rules.items() if length <= distance) + + return score \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/if-statements/content.md b/exercises/practice/darts/.approaches/if-statements/content.md new file mode 100644 index 0000000000..40e2886ddb --- /dev/null +++ b/exercises/practice/darts/.approaches/if-statements/content.md @@ -0,0 +1,73 @@ +# Use `if-statements` + + +```python +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_), (x**2 +y**2) can be calculated instead, and the scoring rings can be adjusted to 1, 25, and 100: + + +```python +# 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 + + +```python +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: + +```python +# 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 +``` + diff --git a/exercises/practice/darts/.approaches/if-statements/snippet.txt b/exercises/practice/darts/.approaches/if-statements/snippet.txt new file mode 100644 index 0000000000..18537416e2 --- /dev/null +++ b/exercises/practice/darts/.approaches/if-statements/snippet.txt @@ -0,0 +1,8 @@ +import math + +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 \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/introduction.md b/exercises/practice/darts/.approaches/introduction.md new file mode 100644 index 0000000000..588c266184 --- /dev/null +++ b/exercises/practice/darts/.approaches/introduction.md @@ -0,0 +1,146 @@ +# Introduction + + +There are multiple Pythonic ways to solve the Darts exercise. +Among them are: + +- Using `if-statements` +- Using a `tuple` (or `list` or `dict`) and a `for-loop` +- Using a `dict` (or `tuple` or `list`) and a `generator-expression` +- Using `boolean` values as `ints` +- Using a `dict` and `dict.get()` +- Using `match/case` (_Python 3.10+ only_) + +
+ +## General guidance + +The goal of the Darts exercise is to score a single throw in a Darts game. +The scoring areas are _concentric circles_, so boundary values need to be checked in order to properly score a throw. +The key is to determine how far from the center the dart lands (_by calculating sqrt(x**2 + y**2), or a variation_) and then determine what scoring ring it falls into. + + +**_Order matters_** - each bigger target circle contains all the smaller circles, so the most straightforward solution is to check the smallest circle first. +Otherwise, you must box your scoring by checking both a _lower bound_ and an _upper bound_. + + +Darts that fall on a _boundary_ are scored based on the area below the line (_closer to center_), so checking `<=` or `>=` is advised. + + +## Approach: Using `if` statements + + +```python +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. +For more details, see the [if statements][approach-if-statements] approach. + + +## Approach: Using a `tuple` and a `loop` + +```python +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` and corresponding`score`. +For more details, see the [tuple and loop][approach-tuple-and-loop] approach. + + +## Approach: Using a `dict` with a `generator-expression` + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1, 200: 0} + + return max(point for distance, point in + rules.items() if throw <= distance) +``` + +This approach is very similar to the [tuple and loop][approach-tuple-and-loop] approach, but iterates over [`dict.items()`][dict-items]. +For more information, see the [dict with generator-expression][approach-dict-with-generator-expression] approach. + + +## Approach: Using Boolean Values as Integers + +```python +def score(x_coord, y_coord): + radius = (x_coord**2 + y_coord**2) + return (radius<=1)*5 + (radius<=25)*4 +(radius<=100)*1 +``` + + +This approach exploits the fact that Boolean values are an integer subtype in Python. +For more information, see the [boolean values as integers][approach-boolean-values-as-integers] approach. + + +## Approach: Using a `Dictionary` and `dict.get()` + +```python +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = { + point <= 100: 1, + point <= 25: 5, + point <= 1: 10 + } + + return scores.get(True, 0) +``` + +This approach uses a dictionary to hold the distance --> scoring mappings and `dict.get()` to retrieve the correct points value. +For more details, read the [`Dictionary and dict.get()`][approach-dict-and-dict-get] approach. + + +## Approach: Using `match/case` (structural pattern matching) + +```python +from math import hypot, ceil + + +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`. +A fallthrough case (`_`) is used if the dart throw is outside the outer circle of the target (_greater than 10_). +For more details, see the [structural pattern matching][approach-struct-pattern-matching] approach. + + +## Which approach to use? + +Many of these approaches are a matter of personal preference - there are not significant memory or performance differences. +Although a strong argument could be made for simplicity and clarity — many listed solutions (_while interesting_) are harder to reason about or are over-engineered for the current scope of the exercise. + +[approach-boolean-values-as-integers]: https://exercism.org/tracks/python/exercises/darts/approaches/boolean-values-as-integers +[approach-dict-and-dict-get]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-and-dict-get +[approach-dict-with-generator-expression]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-with-gnerator-expresson +[approach-if-statements ]: https://exercism.org/tracks/python/exercises/darts/approaches/if-statements +[approach-struct-pattern-matching]: https://exercism.org/tracks/python/exercises/darts/approaches/struct-pattern-matching +[approach-tuple-and-loop]: https://exercism.org/tracks/python/exercises/darts/approaches/tuple-and-loop +[dict-items]: https://docs.python.org/3/library/stdtypes.html#dict.items diff --git a/exercises/practice/darts/.approaches/match-case/content.md b/exercises/practice/darts/.approaches/match-case/content.md new file mode 100644 index 0000000000..04430a5dc5 --- /dev/null +++ b/exercises/practice/darts/.approaches/match-case/content.md @@ -0,0 +1,85 @@ +# Use `match/case` (Structural Pattern Matching) + + +```python +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`][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: + +```python +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: + + +```python +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][assignment-expression] or [walrus operator][walrus] 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: + + +```python +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. + + +[assignment-expression]: https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-assignment_expression +[structural-pattern-matching]: https://peps.python.org/pep-0636/ +[walrus]: https://peps.python.org/pep-0572/ diff --git a/exercises/practice/darts/.approaches/match-case/snippet.txt b/exercises/practice/darts/.approaches/match-case/snippet.txt new file mode 100644 index 0000000000..e66b5382b2 --- /dev/null +++ b/exercises/practice/darts/.approaches/match-case/snippet.txt @@ -0,0 +1,8 @@ +from math import hypot, ceil + +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 \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/tuple-and-loop/content.md b/exercises/practice/darts/.approaches/tuple-and-loop/content.md new file mode 100644 index 0000000000..042b9e88ae --- /dev/null +++ b/exercises/practice/darts/.approaches/tuple-and-loop/content.md @@ -0,0 +1,55 @@ +# Use a tuple with a loop + +```python +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][tuple-unpacking]_). +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: + +```python +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][approach-if-statements ]. +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`. + +[tuple-unpacking]: https://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/#Unpacking_in_a_for_loop +[approach-if-statements ]: https://exercism.org/tracks/python/exercises/darts/approaches/if-statements diff --git a/exercises/practice/darts/.approaches/tuple-and-loop/snippet.txt b/exercises/practice/darts/.approaches/tuple-and-loop/snippet.txt new file mode 100644 index 0000000000..ad50500526 --- /dev/null +++ b/exercises/practice/darts/.approaches/tuple-and-loop/snippet.txt @@ -0,0 +1,7 @@ +def score(x_coord, y_coord): + distance = x_coord**2 + y_coord**2 + rules = (1.0, 10), (25.0, 5), (100.0, 1), (200.0, 0) + + for distance, point in rules: + if length <= distance: + return point \ No newline at end of file From f214698e53d8d30f49eeb3c958781741ae7873ea Mon Sep 17 00:00:00 2001 From: Charles Ross Date: Sat, 30 Nov 2024 16:33:20 -0800 Subject: [PATCH 754/826] Remove extraneous periods (#3833) --- exercises/practice/simple-linked-list/.docs/hints.md | 2 +- .../practice/simple-linked-list/.docs/instructions.append.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/simple-linked-list/.docs/hints.md b/exercises/practice/simple-linked-list/.docs/hints.md index da373540ad..c017108a61 100644 --- a/exercises/practice/simple-linked-list/.docs/hints.md +++ b/exercises/practice/simple-linked-list/.docs/hints.md @@ -13,7 +13,7 @@ In order for _custom objects_ to support `len()`, the special method [`__len__`][__len__] needs to be defined. - Iteration in Python is supported for most sequence, container, or collection type objects. In order for a _custom_ object to support looping or re-ordering, the special method `__iter__` needs to be defined. -[Implementing an iterator for a class.][implementing iterators] can help show you how. +[Implementing an iterator for a class][implementing iterators] can help show you how. [Baeldung: The Stack Data Structure]: https://www.baeldung.com/cs/stack-data-structure [Koder Dojo Coding an ADS Stack in Python]: https://www.koderdojo.com/blog/coding-a-stack-abstract-data-structure-using-linked-list-in-python diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/instructions.append.md index 888e675a84..7f848fbaab 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.append.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.append.md @@ -46,7 +46,7 @@ For details on implementing special or "dunder" methods in Python, see [Python D ## Building an Iterator To support looping through or reversing your `LinkedList`, you will need to implement the `__iter__` special method. -See [implementing an iterator for a class.][custom iterators] for implementation details. +See [implementing an iterator for a class][custom iterators] for implementation details.
@@ -56,7 +56,7 @@ Sometimes it is necessary to both [customize][customize errors] and [`raise`][ra When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. -Custom exceptions can be created through new exception classes (see [`classes`][classes tutorial] for more detail.) that are typically subclasses of [`Exception`][exception base class]. +Custom exceptions can be created through new exception classes (see [`classes`][classes tutorial] for more detail) that are typically subclasses of [`Exception`][exception base class]. For situations where you know the error source will be a derivative of a certain exception _type_, you can choose to inherit from one of the [`built in error types`][built-in errors] under the _Exception_ class. When raising the error, you should still include a meaningful message. From 26635914c1f754dbb38bbb89f511536bda7a2701 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 1 Dec 2024 22:30:59 -0800 Subject: [PATCH 755/826] [Simple Linked List]: Updated Stub File (#3834) * Reworked stub to clean up mutable default argument and missing special method. * Added bethanyg as contributor. --- .../practice/simple-linked-list/.meta/config.json | 1 + .../simple-linked-list/simple_linked_list.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/exercises/practice/simple-linked-list/.meta/config.json b/exercises/practice/simple-linked-list/.meta/config.json index 2fc136a325..2134b49237 100644 --- a/exercises/practice/simple-linked-list/.meta/config.json +++ b/exercises/practice/simple-linked-list/.meta/config.json @@ -3,6 +3,7 @@ "cmccandless" ], "contributors": [ + "Bethanyg", "Dog", "N-Parsons", "rootulp", diff --git a/exercises/practice/simple-linked-list/simple_linked_list.py b/exercises/practice/simple-linked-list/simple_linked_list.py index cbf120e2fc..dfb9e6c979 100644 --- a/exercises/practice/simple-linked-list/simple_linked_list.py +++ b/exercises/practice/simple-linked-list/simple_linked_list.py @@ -1,3 +1,7 @@ +class EmptyListException(Exception): + pass + + class Node: def __init__(self, value): pass @@ -10,7 +14,10 @@ def next(self): class LinkedList: - def __init__(self, values=[]): + def __init__(self, values=None): + pass + + def __iter__(self): pass def __len__(self): @@ -27,7 +34,3 @@ def pop(self): def reversed(self): pass - - -class EmptyListException(Exception): - pass From 7bec634f5c51cce82d233ad88f7ae81a3e98242a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 2 Dec 2024 12:45:49 -0800 Subject: [PATCH 756/826] Revert "Testing fix for no-important-files workflow. (#3824)" (#3835) This reverts commit 8dde6fd43646a1e071aca2d5a1a324b503dc47a6. --- .github/workflows/no-important-files-changed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index cfcd6db4d4..812e912966 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -17,7 +17,7 @@ permissions: jobs: check: - uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@fix-no-important-files + uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main with: repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} ref: ${{ github.head_ref }} From 07bf542d0ff9aa4ae5954688679014e9ccb75310 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Jan 2025 21:30:04 -0800 Subject: [PATCH 757/826] Updated test cases for pig-latin and regenerated test file. (#3839) --- exercises/practice/pig-latin/.meta/tests.toml | 60 +++++++++++-------- .../practice/pig-latin/pig_latin_test.py | 5 +- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/exercises/practice/pig-latin/.meta/tests.toml b/exercises/practice/pig-latin/.meta/tests.toml index 49ce6e110e..d524305b45 100644 --- a/exercises/practice/pig-latin/.meta/tests.toml +++ b/exercises/practice/pig-latin/.meta/tests.toml @@ -1,69 +1,79 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [11567f84-e8c6-4918-aedb-435f0b73db57] -description = "word beginning with a" +description = "ay is added to words that start with vowels -> word beginning with a" [f623f581-bc59-4f45-9032-90c3ca9d2d90] -description = "word beginning with e" +description = "ay is added to words that start with vowels -> word beginning with e" [7dcb08b3-23a6-4e8a-b9aa-d4e859450d58] -description = "word beginning with i" +description = "ay is added to words that start with vowels -> word beginning with i" [0e5c3bff-266d-41c8-909f-364e4d16e09c] -description = "word beginning with o" +description = "ay is added to words that start with vowels -> word beginning with o" [614ba363-ca3c-4e96-ab09-c7320799723c] -description = "word beginning with u" +description = "ay is added to words that start with vowels -> word beginning with u" [bf2538c6-69eb-4fa7-a494-5a3fec911326] -description = "word beginning with a vowel and followed by a qu" +description = "ay is added to words that start with vowels -> word beginning with a vowel and followed by a qu" [e5be8a01-2d8a-45eb-abb4-3fcc9582a303] -description = "word beginning with p" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with p" [d36d1e13-a7ed-464d-a282-8820cb2261ce] -description = "word beginning with k" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with k" [d838b56f-0a89-4c90-b326-f16ff4e1dddc] -description = "word beginning with x" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with x" [bce94a7a-a94e-4e2b-80f4-b2bb02e40f71] -description = "word beginning with q without a following u" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with q without a following u" + +[e59dbbe8-ccee-4619-a8e9-ce017489bfc0] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with consonant and vowel containing qu" [c01e049a-e3e2-451c-bf8e-e2abb7e438b8] -description = "word beginning with ch" +description = "some letter clusters are treated like a single consonant -> word beginning with ch" [9ba1669e-c43f-4b93-837a-cfc731fd1425] -description = "word beginning with qu" +description = "some letter clusters are treated like a single consonant -> word beginning with qu" [92e82277-d5e4-43d7-8dd3-3a3b316c41f7] -description = "word beginning with qu and a preceding consonant" +description = "some letter clusters are treated like a single consonant -> word beginning with qu and a preceding consonant" [79ae4248-3499-4d5b-af46-5cb05fa073ac] -description = "word beginning with th" +description = "some letter clusters are treated like a single consonant -> word beginning with th" [e0b3ae65-f508-4de3-8999-19c2f8e243e1] -description = "word beginning with thr" +description = "some letter clusters are treated like a single consonant -> word beginning with thr" [20bc19f9-5a35-4341-9d69-1627d6ee6b43] -description = "word beginning with sch" +description = "some letter clusters are treated like a single consonant -> word beginning with sch" [54b796cb-613d-4509-8c82-8fbf8fc0af9e] -description = "word beginning with yt" +description = "some letter clusters are treated like a single vowel -> word beginning with yt" [8c37c5e1-872e-4630-ba6e-d20a959b67f6] -description = "word beginning with xr" +description = "some letter clusters are treated like a single vowel -> word beginning with xr" [a4a36d33-96f3-422c-a233-d4021460ff00] -description = "y is treated like a consonant at the beginning of a word" +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a consonant at the beginning of a word" [adc90017-1a12-4100-b595-e346105042c7] -description = "y is treated like a vowel at the end of a consonant cluster" +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a vowel at the end of a consonant cluster" [29b4ca3d-efe5-4a95-9a54-8467f2e5e59a] -description = "y as second letter in two letter word" +description = "position of y in a word determines if it is a consonant or a vowel -> y as second letter in two letter word" [44616581-5ce3-4a81-82d0-40c7ab13d2cf] -description = "a whole phrase" +description = "phrases are translated -> a whole phrase" diff --git a/exercises/practice/pig-latin/pig_latin_test.py b/exercises/practice/pig-latin/pig_latin_test.py index e5a441eb6b..1217d6883f 100644 --- a/exercises/practice/pig-latin/pig_latin_test.py +++ b/exercises/practice/pig-latin/pig_latin_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-01-10 import unittest @@ -40,6 +40,9 @@ def test_word_beginning_with_x(self): def test_word_beginning_with_q_without_a_following_u(self): self.assertEqual(translate("qat"), "atqay") + def test_word_beginning_with_consonant_and_vowel_containing_qu(self): + self.assertEqual(translate("liquid"), "iquidlay") + def test_word_beginning_with_ch(self): self.assertEqual(translate("chair"), "airchay") From f94d0fd31e2c3379b6fef840d104ff9d31002a91 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Jan 2025 21:40:56 -0800 Subject: [PATCH 758/826] Updated bob tests.toml and regenerated test file. (#3840) --- exercises/practice/bob/.meta/tests.toml | 18 +++++++++++++++--- exercises/practice/bob/bob_test.py | 13 ++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml index 6304855792..5299e2895f 100644 --- a/exercises/practice/bob/.meta/tests.toml +++ b/exercises/practice/bob/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [e162fead-606f-437a-a166-d051915cea8e] description = "stating something" @@ -64,6 +71,7 @@ description = "alternate silence" [66953780-165b-4e7e-8ce3-4bcb80b6385a] description = "multiple line question" +include = false [5371ef75-d9ea-4103-bcfa-2da973ddec1b] description = "starting with whitespace" @@ -76,3 +84,7 @@ description = "other whitespace" [12983553-8601-46a8-92fa-fcaa3bc4a2a0] description = "non-question ending with whitespace" + +[2c7278ac-f955-4eb4-bf8f-e33eb4116a15] +description = "multiple line question" +reimplements = "66953780-165b-4e7e-8ce3-4bcb80b6385a" diff --git a/exercises/practice/bob/bob_test.py b/exercises/practice/bob/bob_test.py index faba5f9612..755d5c935e 100644 --- a/exercises/practice/bob/bob_test.py +++ b/exercises/practice/bob/bob_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json -# File last updated on 2023-07-20 +# File last updated on 2025-01-10 import unittest @@ -79,12 +79,6 @@ def test_prolonged_silence(self): def test_alternate_silence(self): self.assertEqual(response("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!") - def test_multiple_line_question(self): - self.assertEqual( - response("\nDoes this cryogenic chamber make me look fat?\nNo."), - "Whatever.", - ) - def test_starting_with_whitespace(self): self.assertEqual(response(" hmmmmmmm..."), "Whatever.") @@ -100,3 +94,8 @@ def test_non_question_ending_with_whitespace(self): self.assertEqual( response("This is a statement ending with whitespace "), "Whatever." ) + + def test_multiple_line_question(self): + self.assertEqual( + response("\nDoes this cryogenic chamber make\n me look fat?"), "Sure." + ) From 8ddd19549b09c13ff21e0da01c5f4e6ede8985ce Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Jan 2025 21:41:46 -0800 Subject: [PATCH 759/826] Synced metadata for practice exercises. (#3841) --- exercises/practice/atbash-cipher/.meta/config.json | 2 +- exercises/practice/collatz-conjecture/.meta/config.json | 4 ++-- exercises/practice/pythagorean-triplet/.meta/config.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json index 9f678c6f20..5df506281a 100644 --- a/exercises/practice/atbash-cipher/.meta/config.json +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -27,7 +27,7 @@ ".meta/example.py" ] }, - "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.", + "blurb": "Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Atbash" } diff --git a/exercises/practice/collatz-conjecture/.meta/config.json b/exercises/practice/collatz-conjecture/.meta/config.json index e5eda73e1d..cfed91f3bd 100644 --- a/exercises/practice/collatz-conjecture/.meta/config.json +++ b/exercises/practice/collatz-conjecture/.meta/config.json @@ -26,6 +26,6 @@ ] }, "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.", - "source": "An unsolved problem in mathematics named after mathematician Lothar Collatz", - "source_url": "https://en.wikipedia.org/wiki/3x_%2B_1_problem" + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture" } diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index a9bed96083..040a45106f 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -31,7 +31,7 @@ ".meta/example.py" ] }, - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the triplet.", - "source": "Problem 9 at Project Euler", + "blurb": "Given an integer N, find all Pythagorean triplets for which a + b + c = N.", + "source": "A variation of Problem 9 from Project Euler", "source_url": "https://projecteuler.net/problem=9" } From c8b3a3958f12bda1158997763f9f63f6c8f81ed2 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Jan 2025 21:42:10 -0800 Subject: [PATCH 760/826] Synced practice exercise intros and instructions to problem-specs. (#3842) --- .../affine-cipher/.docs/instructions.md | 2 +- .../atbash-cipher/.docs/instructions.md | 2 +- .../practice/change/.docs/instructions.md | 14 +-- .../practice/change/.docs/introduction.md | 26 +++++ .../collatz-conjecture/.docs/instructions.md | 28 +---- .../collatz-conjecture/.docs/introduction.md | 28 +++++ .../complex-numbers/.docs/instructions.md | 107 +++++++++++++++--- .../practice/dominoes/.docs/instructions.md | 4 +- .../practice/dominoes/.docs/introduction.md | 13 +++ .../eliuds-eggs/.docs/introduction.md | 48 +++++--- .../grade-school/.docs/instructions.md | 20 ++-- .../practice/hamming/.docs/instructions.md | 11 -- .../practice/hamming/.docs/introduction.md | 12 ++ .../practice/knapsack/.docs/instructions.md | 8 +- .../practice/knapsack/.docs/introduction.md | 12 +- exercises/practice/luhn/.docs/instructions.md | 8 +- exercises/practice/luhn/.docs/introduction.md | 11 ++ .../pascals-triangle/.docs/introduction.md | 2 +- .../phone-number/.docs/introduction.md | 12 ++ .../protein-translation/.docs/instructions.md | 8 +- .../pythagorean-triplet/.docs/instructions.md | 2 +- .../pythagorean-triplet/.docs/introduction.md | 19 ++++ .../square-root/.docs/instructions.md | 17 ++- .../square-root/.docs/introduction.md | 10 ++ 24 files changed, 304 insertions(+), 120 deletions(-) create mode 100644 exercises/practice/change/.docs/introduction.md create mode 100644 exercises/practice/collatz-conjecture/.docs/introduction.md create mode 100644 exercises/practice/dominoes/.docs/introduction.md create mode 100644 exercises/practice/hamming/.docs/introduction.md create mode 100644 exercises/practice/luhn/.docs/introduction.md create mode 100644 exercises/practice/phone-number/.docs/introduction.md create mode 100644 exercises/practice/pythagorean-triplet/.docs/introduction.md create mode 100644 exercises/practice/square-root/.docs/introduction.md diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 4eff918de7..f6329db936 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -4,7 +4,7 @@ Create an implementation of the affine cipher, an ancient encryption system crea The affine cipher is a type of monoalphabetic substitution cipher. Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value. -Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the atbash cipher, because it has many more keys. +Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the Atbash cipher, because it has many more keys. [//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic " diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md index 21ca2ce0aa..1e7627b1e5 100644 --- a/exercises/practice/atbash-cipher/.docs/instructions.md +++ b/exercises/practice/atbash-cipher/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East. +Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East. The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. The first letter is replaced with the last letter, the second with the second-last, and so on. diff --git a/exercises/practice/change/.docs/instructions.md b/exercises/practice/change/.docs/instructions.md index 30fa567750..5887f4cb69 100644 --- a/exercises/practice/change/.docs/instructions.md +++ b/exercises/practice/change/.docs/instructions.md @@ -1,14 +1,8 @@ # Instructions -Correctly determine the fewest number of coins to be given to a customer such that the sum of the coins' value would equal the correct amount of change. +Determine the fewest number of coins to give a customer so that the sum of their values equals the correct amount of change. -## For example +## Examples -- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) or [5, 10] -- An input of 40 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) and one quarter (25) or [5, 10, 25] - -## Edge cases - -- Does your algorithm work for any given set of coins? -- Can you ask for negative change? -- Can you ask for a change value smaller than the smallest coin value? +- An amount of 15 with available coin values [1, 5, 10, 25, 100] should return one coin of value 5 and one coin of value 10, or [5, 10]. +- An amount of 40 with available coin values [1, 5, 10, 25, 100] should return one coin of value 5, one coin of value 10, and one coin of value 25, or [5, 10, 25]. diff --git a/exercises/practice/change/.docs/introduction.md b/exercises/practice/change/.docs/introduction.md new file mode 100644 index 0000000000..b4f8308a1b --- /dev/null +++ b/exercises/practice/change/.docs/introduction.md @@ -0,0 +1,26 @@ +# Introduction + +In the mystical village of Coinholt, you stand behind the counter of your bakery, arranging a fresh batch of pastries. +The door creaks open, and in walks Denara, a skilled merchant with a keen eye for quality goods. +After a quick meal, she slides a shimmering coin across the counter, representing a value of 100 units. + +You smile, taking the coin, and glance at the total cost of the meal: 88 units. +That means you need to return 12 units in change. + +Denara holds out her hand expectantly. +"Just give me the fewest coins," she says with a smile. +"My pouch is already full, and I don't want to risk losing them on the road." + +You know you have a few options. +"We have Lumis (worth 10 units), Viras (worth 5 units), and Zenth (worth 2 units) available for change." + +You quickly calculate the possibilities in your head: + +- one Lumis (1 × 10 units) + one Zenth (1 × 2 units) = 2 coins total +- two Viras (2 × 5 units) + one Zenth (1 × 2 units) = 3 coins total +- six Zenth (6 × 2 units) = 6 coins total + +"The best choice is two coins: one Lumis and one Zenth," you say, handing her the change. + +Denara smiles, clearly impressed. +"As always, you've got it right." diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.md b/exercises/practice/collatz-conjecture/.docs/instructions.md index ba060483e4..af332a810f 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.md @@ -1,29 +1,3 @@ # Instructions -The Collatz Conjecture or 3x+1 problem can be summarized as follows: - -Take any positive integer n. -If n is even, divide n by 2 to get n / 2. -If n is odd, multiply n by 3 and add 1 to get 3n + 1. -Repeat the process indefinitely. -The conjecture states that no matter which number you start with, you will always reach 1 eventually. - -Given a number n, return the number of steps required to reach 1. - -## Examples - -Starting with n = 12, the steps would be as follows: - -0. 12 -1. 6 -2. 3 -3. 10 -4. 5 -5. 16 -6. 8 -7. 4 -8. 2 -9. 1 - -Resulting in 9 steps. -So for input n = 12, the return value would be 9. +Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture. diff --git a/exercises/practice/collatz-conjecture/.docs/introduction.md b/exercises/practice/collatz-conjecture/.docs/introduction.md new file mode 100644 index 0000000000..c35bdeb67d --- /dev/null +++ b/exercises/practice/collatz-conjecture/.docs/introduction.md @@ -0,0 +1,28 @@ +# Introduction + +One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea. +On one page, a single question stood out: **Can every number find its way to 1?** +It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades. + +The rules were deceptively simple. +Pick any positive integer. + +- If it's even, divide it by 2. +- If it's odd, multiply it by 3 and add 1. + +Then, repeat these steps with the result, continuing indefinitely. + +Curious, you picked number 12 to test and began the journey: + +12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 + +Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing. +At first, the sequence seemed unpredictable — jumping up, down, and all over. +Yet, the conjecture claims that no matter the starting number, we'll always end at 1. + +It was fascinating, but also puzzling. +Why does this always seem to work? +Could there be a number where the process breaks down, looping forever or escaping into infinity? +The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets. + +[collatz-prize]: https://mathprize.net/posts/collatz-conjecture/ diff --git a/exercises/practice/complex-numbers/.docs/instructions.md b/exercises/practice/complex-numbers/.docs/instructions.md index 50b19aedff..2b8a7a49d8 100644 --- a/exercises/practice/complex-numbers/.docs/instructions.md +++ b/exercises/practice/complex-numbers/.docs/instructions.md @@ -1,29 +1,100 @@ # Instructions -A complex number is a number in the form `a + b * i` where `a` and `b` are real and `i` satisfies `i^2 = -1`. +A **complex number** is expressed in the form `z = a + b * i`, where: -`a` is called the real part and `b` is called the imaginary part of `z`. -The conjugate of the number `a + b * i` is the number `a - b * i`. -The absolute value of a complex number `z = a + b * i` is a real number `|z| = sqrt(a^2 + b^2)`. The square of the absolute value `|z|^2` is the result of multiplication of `z` by its complex conjugate. +- `a` is the **real part** (a real number), -The sum/difference of two complex numbers involves adding/subtracting their real and imaginary parts separately: -`(a + i * b) + (c + i * d) = (a + c) + (b + d) * i`, -`(a + i * b) - (c + i * d) = (a - c) + (b - d) * i`. +- `b` is the **imaginary part** (also a real number), and -Multiplication result is by definition -`(a + i * b) * (c + i * d) = (a * c - b * d) + (b * c + a * d) * i`. +- `i` is the **imaginary unit** satisfying `i^2 = -1`. -The reciprocal of a non-zero complex number is -`1 / (a + i * b) = a/(a^2 + b^2) - b/(a^2 + b^2) * i`. +## Operations on Complex Numbers -Dividing a complex number `a + i * b` by another `c + i * d` gives: -`(a + i * b) / (c + i * d) = (a * c + b * d)/(c^2 + d^2) + (b * c - a * d)/(c^2 + d^2) * i`. +### Conjugate -Raising e to a complex exponent can be expressed as `e^(a + i * b) = e^a * e^(i * b)`, the last term of which is given by Euler's formula `e^(i * b) = cos(b) + i * sin(b)`. +The conjugate of the complex number `z = a + b * i` is given by: -Implement the following operations: +```text +zc = a - b * i +``` -- addition, subtraction, multiplication and division of two complex numbers, -- conjugate, absolute value, exponent of a given complex number. +### Absolute Value -Assume the programming language you are using does not have an implementation of complex numbers. +The absolute value (or modulus) of `z` is defined as: + +```text +|z| = sqrt(a^2 + b^2) +``` + +The square of the absolute value is computed as the product of `z` and its conjugate `zc`: + +```text +|z|^2 = z * zc = a^2 + b^2 +``` + +### Addition + +The sum of two complex numbers `z1 = a + b * i` and `z2 = c + d * i` is computed by adding their real and imaginary parts separately: + +```text +z1 + z2 = (a + b * i) + (c + d * i) + = (a + c) + (b + d) * i +``` + +### Subtraction + +The difference of two complex numbers is obtained by subtracting their respective parts: + +```text +z1 - z2 = (a + b * i) - (c + d * i) + = (a - c) + (b - d) * i +``` + +### Multiplication + +The product of two complex numbers is defined as: + +```text +z1 * z2 = (a + b * i) * (c + d * i) + = (a * c - b * d) + (b * c + a * d) * i +``` + +### Reciprocal + +The reciprocal of a non-zero complex number is given by: + +```text +1 / z = 1 / (a + b * i) + = a / (a^2 + b^2) - b / (a^2 + b^2) * i +``` + +### Division + +The division of one complex number by another is given by: + +```text +z1 / z2 = z1 * (1 / z2) + = (a + b * i) / (c + d * i) + = (a * c + b * d) / (c^2 + d^2) + (b * c - a * d) / (c^2 + d^2) * i +``` + +### Exponentiation + +Raising _e_ (the base of the natural logarithm) to a complex exponent can be expressed using Euler's formula: + +```text +e^(a + b * i) = e^a * e^(b * i) + = e^a * (cos(b) + i * sin(b)) +``` + +## Implementation Requirements + +Given that you should not use built-in support for complex numbers, implement the following operations: + +- **addition** of two complex numbers +- **subtraction** of two complex numbers +- **multiplication** of two complex numbers +- **division** of two complex numbers +- **conjugate** of a complex number +- **absolute value** of a complex number +- **exponentiation** of _e_ (the base of the natural logarithm) to a complex number diff --git a/exercises/practice/dominoes/.docs/instructions.md b/exercises/practice/dominoes/.docs/instructions.md index 1ced9f6448..75055b9e89 100644 --- a/exercises/practice/dominoes/.docs/instructions.md +++ b/exercises/practice/dominoes/.docs/instructions.md @@ -2,7 +2,9 @@ Make a chain of dominoes. -Compute a way to order a given set of dominoes in such a way that they form a correct domino chain (the dots on one half of a stone match the dots on the neighboring half of an adjacent stone) and that dots on the halves of the stones which don't have a neighbor (the first and last stone) match each other. +Compute a way to order a given set of domino stones so that they form a correct domino chain. +In the chain, the dots on one half of a stone must match the dots on the neighboring half of an adjacent stone. +Additionally, the dots on the halves of the stones without neighbors (the first and last stone) must match each other. For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same. diff --git a/exercises/practice/dominoes/.docs/introduction.md b/exercises/practice/dominoes/.docs/introduction.md new file mode 100644 index 0000000000..df248c2116 --- /dev/null +++ b/exercises/practice/dominoes/.docs/introduction.md @@ -0,0 +1,13 @@ +# Introduction + +In Toyland, the trains are always busy delivering treasures across the city, from shiny marbles to rare building blocks. +The tracks they run on are made of colorful domino-shaped pieces, each marked with two numbers. +For the trains to move, the dominoes must form a perfect chain where the numbers match. + +Today, an urgent delivery of rare toys is on hold. +You've been handed a set of track pieces to inspect. +If they can form a continuous chain, the train will be on its way, bringing smiles across Toyland. +If not, the set will be discarded, and another will be tried. + +The toys are counting on you to solve this puzzle. +Will the dominoes connect the tracks and send the train rolling, or will the set be left behind? diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md index 49eaffd8bc..8198974809 100644 --- a/exercises/practice/eliuds-eggs/.docs/introduction.md +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -12,36 +12,54 @@ The position information encoding is calculated as follows: 2. Convert the number from binary to decimal. 3. Show the result on the display. -Example 1: +## Example 1 + +![Seven individual nest boxes arranged in a row whose first, third, fourth and seventh nests each have a single egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-coop.svg) ```text -Chicken Coop: _ _ _ _ _ _ _ |E| |E|E| | |E| +``` + +### Resulting Binary + +![1011001](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-binary.svg) + +```text + _ _ _ _ _ _ _ +|1|0|1|1|0|0|1| +``` -Resulting Binary: - 1 0 1 1 0 0 1 +### Decimal number on the display -Decimal number on the display: 89 -Actual eggs in the coop: +### Actual eggs in the coop + 4 + +## Example 2 + +![Seven individual nest boxes arranged in a row where only the fourth nest has an egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-coop.svg) + +```text + _ _ _ _ _ _ _ +| | | |E| | | | ``` -Example 2: +### Resulting Binary + +![0001000](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-binary.svg) ```text -Chicken Coop: - _ _ _ _ _ _ _ _ -| | | |E| | | | | + _ _ _ _ _ _ _ +|0|0|0|1|0|0|0| +``` -Resulting Binary: - 0 0 0 1 0 0 0 0 +### Decimal number on the display -Decimal number on the display: 16 -Actual eggs in the coop: +### Actual eggs in the coop + 1 -``` diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md index 9a63e398d8..3cb1b5d5f9 100644 --- a/exercises/practice/grade-school/.docs/instructions.md +++ b/exercises/practice/grade-school/.docs/instructions.md @@ -1,21 +1,21 @@ # Instructions -Given students' names along with the grade that they are in, create a roster for the school. +Given students' names along with the grade they are in, create a roster for the school. In the end, you should be able to: -- Add a student's name to the roster for a grade +- Add a student's name to the roster for a grade: - "Add Jim to grade 2." - "OK." -- Get a list of all students enrolled in a grade +- Get a list of all students enrolled in a grade: - "Which students are in grade 2?" - - "We've only got Jim just now." + - "We've only got Jim right now." - Get a sorted list of all students in all grades. - Grades should sort as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. - - "Who all is enrolled in school right now?" + Grades should be sorted as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. + - "Who is enrolled in school right now?" - "Let me think. - We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2 and Jim in grade 5. - So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" + We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2, and Jim in grade 5. + So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe, and Jim." -Note that all our students only have one name (It's a small town, what do you want?) and each student cannot be added more than once to a grade or the roster. -In fact, when a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. +Note that all our students only have one name (it's a small town, what do you want?), and each student cannot be added more than once to a grade or the roster. +If a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index b9ae6efc51..8f47a179e0 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -2,15 +2,6 @@ Calculate the Hamming distance between two DNA strands. -Your body is made up of cells that contain DNA. -Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. -In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! - -When cells divide, their DNA replicates too. -Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. -If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. -This is known as the "Hamming distance". - We read DNA using the letters C, A, G and T. Two strands might look like this: @@ -20,8 +11,6 @@ Two strands might look like this: They have 7 differences, and therefore the Hamming distance is 7. -The Hamming distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) - ## Implementation notes The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work. diff --git a/exercises/practice/hamming/.docs/introduction.md b/exercises/practice/hamming/.docs/introduction.md new file mode 100644 index 0000000000..8419bf479e --- /dev/null +++ b/exercises/practice/hamming/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +Your body is made up of cells that contain DNA. +Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. +In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! + +When cells divide, their DNA replicates too. +Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. +If we compare two strands of DNA and count the differences between them, we can see how many mistakes occurred. +This is known as the "Hamming distance". + +The Hamming distance is useful in many areas of science, not just biology, so it's a nice phrase to be familiar with :) diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index 3411db9886..0ebf7914c5 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -1,11 +1,11 @@ # Instructions -Your task is to determine which items to take so that the total value of his selection is maximized, taking into account the knapsack's carrying capacity. +Your task is to determine which items to take so that the total value of her selection is maximized, taking into account the knapsack's carrying capacity. Items will be represented as a list of items. Each item will have a weight and value. All values given will be strictly positive. -Bob can take only one of each item. +Lhakpa can take only one of each item. For example: @@ -21,5 +21,5 @@ Knapsack Maximum Weight: 10 ``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. -In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. -He cannot get more than 90 as his knapsack has a weight limit of 10. +In this example, Lhakpa should take the second and fourth item to maximize her value, which, in this case, is 90. +She cannot get more than 90 as her knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md index 9b2bed8b4e..9ac9df596b 100644 --- a/exercises/practice/knapsack/.docs/introduction.md +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -1,8 +1,10 @@ # Introduction -Bob is a thief. -After months of careful planning, he finally manages to crack the security systems of a fancy store. +Lhakpa is a [Sherpa][sherpa] mountain guide and porter. +After months of careful planning, the expedition Lhakpa works for is about to leave. +She will be paid the value she carried to the base camp. -In front of him are many items, each with a value and weight. -Bob would gladly take all of the items, but his knapsack can only hold so much weight. -Bob has to carefully consider which items to take so that the total value of his selection is maximized. +In front of her are many items, each with a value and weight. +Lhakpa would gladly take all of the items, but her knapsack can only hold so much weight. + +[sherpa]: https://en.wikipedia.org/wiki/Sherpa_people#Mountaineering diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 49934c1064..5bbf007b07 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -1,12 +1,10 @@ # Instructions -Given a number determine whether or not it is valid per the Luhn formula. +Determine whether a credit card number is valid according to the [Luhn formula][luhn]. -The [Luhn algorithm][luhn] is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers. +The number will be provided as a string. -The task is to check if a given string is valid. - -## Validating a Number +## Validating a number Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. diff --git a/exercises/practice/luhn/.docs/introduction.md b/exercises/practice/luhn/.docs/introduction.md new file mode 100644 index 0000000000..ec2bd709d2 --- /dev/null +++ b/exercises/practice/luhn/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +At the Global Verification Authority, you've just been entrusted with a critical assignment. +Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. +The Luhn algorithm is a simple checksum formula used to ensure these numbers are valid and error-free. + +A batch of identifiers has just arrived on your desk. +All of them must pass the Luhn test to ensure they're legitimate. +If any fail, they'll be flagged as invalid, preventing errors or fraud, such as incorrect transactions or unauthorized access. + +Can you ensure this is done right? The integrity of many services depends on you. diff --git a/exercises/practice/pascals-triangle/.docs/introduction.md b/exercises/practice/pascals-triangle/.docs/introduction.md index 60b8ec30dc..eab454e5a6 100644 --- a/exercises/practice/pascals-triangle/.docs/introduction.md +++ b/exercises/practice/pascals-triangle/.docs/introduction.md @@ -13,7 +13,7 @@ Over the next hour, your teacher reveals some amazing things hidden in this tria - It contains the Fibonacci sequence. - If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. -The teacher implores you and your classmates to lookup other uses, and assures you that there are lots more! +The teacher implores you and your classmates to look up other uses, and assures you that there are lots more! At that moment, the school bell rings. You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. diff --git a/exercises/practice/phone-number/.docs/introduction.md b/exercises/practice/phone-number/.docs/introduction.md new file mode 100644 index 0000000000..c4142c5af7 --- /dev/null +++ b/exercises/practice/phone-number/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +You've joined LinkLine, a leading communications company working to ensure reliable connections for everyone. +The team faces a big challenge: users submit phone numbers in all sorts of formats — dashes, spaces, dots, parentheses, and even prefixes. +Some numbers are valid, while others are impossible to use. + +Your mission is to turn this chaos into order. +You'll clean up valid numbers, formatting them appropriately for use in the system. +At the same time, you'll identify and filter out any invalid entries. + +The success of LinkLine's operations depends on your ability to separate the useful from the unusable. +Are you ready to take on the challenge and keep the connections running smoothly? diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index 7dc34d2edf..44880802c5 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -2,12 +2,12 @@ Translate RNA sequences into proteins. -RNA can be broken into three nucleotide sequences called codons, and then translated to a polypeptide like so: +RNA can be broken into three-nucleotide sequences called codons, and then translated to a protein like so: RNA: `"AUGUUUUCU"` => translates to Codons: `"AUG", "UUU", "UCU"` -=> which become a polypeptide with the following sequence => +=> which become a protein with the following sequence => Protein: `"Methionine", "Phenylalanine", "Serine"` @@ -27,9 +27,9 @@ Protein: `"Methionine", "Phenylalanine", "Serine"` Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. -Below are the codons and resulting Amino Acids needed for the exercise. +Below are the codons and resulting amino acids needed for the exercise. -| Codon | Protein | +| Codon | Amino Acid | | :----------------- | :------------ | | AUG | Methionine | | UUU, UUC | Phenylalanine | diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.md b/exercises/practice/pythagorean-triplet/.docs/instructions.md index 1c1a8aea61..ced833d7a5 100644 --- a/exercises/practice/pythagorean-triplet/.docs/instructions.md +++ b/exercises/practice/pythagorean-triplet/.docs/instructions.md @@ -1,4 +1,4 @@ -# Instructions +# Description A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which, diff --git a/exercises/practice/pythagorean-triplet/.docs/introduction.md b/exercises/practice/pythagorean-triplet/.docs/introduction.md new file mode 100644 index 0000000000..3453c6ed48 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.docs/introduction.md @@ -0,0 +1,19 @@ +# Introduction + +You are an accomplished problem-solver, known for your ability to tackle the most challenging mathematical puzzles. +One evening, you receive an urgent letter from an inventor called the Triangle Tinkerer, who is working on a groundbreaking new project. +The letter reads: + +> Dear Mathematician, +> +> I need your help. +> I am designing a device that relies on the unique properties of Pythagorean triplets — sets of three integers that satisfy the equation a² + b² = c². +> This device will revolutionize navigation, but for it to work, I must program it with every possible triplet where the sum of a, b, and c equals a specific number, N. +> Calculating these triplets by hand would take me years, but I hear you are more than up to the task. +> +> Time is of the essence. +> The future of my invention — and perhaps even the future of mathematical innovation — rests on your ability to solve this problem. + +Motivated by the importance of the task, you set out to find all Pythagorean triplets that satisfy the condition. +Your work could have far-reaching implications, unlocking new possibilities in science and engineering. +Can you rise to the challenge and make history? diff --git a/exercises/practice/square-root/.docs/instructions.md b/exercises/practice/square-root/.docs/instructions.md index e9905e9d41..d258b86876 100644 --- a/exercises/practice/square-root/.docs/instructions.md +++ b/exercises/practice/square-root/.docs/instructions.md @@ -1,13 +1,18 @@ # Instructions -Given a natural radicand, return its square root. +Your task is to calculate the square root of a given number. -Note that the term "radicand" refers to the number for which the root is to be determined. -That is, it is the number under the root symbol. +- Try to avoid using the pre-existing math libraries of your language. +- As input you'll be given a positive whole number, i.e. 1, 2, 3, 4… +- You are only required to handle cases where the result is a positive whole number. -Check out the Wikipedia pages on [square root][square-root] and [methods of computing square roots][computing-square-roots]. +Some potential approaches: -Recall also that natural numbers are positive real whole numbers (i.e. 1, 2, 3 and up). +- Linear or binary search for a number that gives the input number when squared. +- Successive approximation using Newton's or Heron's method. +- Calculating one digit at a time or one bit at a time. -[square-root]: https://en.wikipedia.org/wiki/Square_root +You can check out the Wikipedia pages on [integer square root][integer-square-root] and [methods of computing square roots][computing-square-roots] to help with choosing a method of calculation. + +[integer-square-root]: https://en.wikipedia.org/wiki/Integer_square_root [computing-square-roots]: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots diff --git a/exercises/practice/square-root/.docs/introduction.md b/exercises/practice/square-root/.docs/introduction.md new file mode 100644 index 0000000000..1d692934f2 --- /dev/null +++ b/exercises/practice/square-root/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +We are launching a deep space exploration rocket and we need a way to make sure the navigation system stays on target. + +As the first step in our calculation, we take a target number and find its square root (that is, the number that when multiplied by itself equals the target number). + +The journey will be very long. +To make the batteries last as long as possible, we had to make our rocket's onboard computer very power efficient. +Unfortunately that means that we can't rely on fancy math libraries and functions, as they use more power. +Instead we want to implement our own square root calculation. From 8141d07da3c486471760c1c9fac8cfad6f178986 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 16 Jan 2025 16:02:39 -0800 Subject: [PATCH 761/826] Synced Anagram instructions to problem specifications. (#3849) --- exercises/practice/anagram/.docs/instructions.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index a7298485b3..dca24f5262 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,13 +1,12 @@ # Instructions -Your task is to, given a target word and a set of candidate words, to find the subset of the candidates that are anagrams of the target. +Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target. An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. -The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). -Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`. -The anagram set is the subset of the candidate set that are anagrams of the target (in any order). -Words in the anagram set should have the same letter case as in the candidate set. +The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). +Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`. +The words you need to find should be taken from the candidate words, using the same letter case. -Given the target `"stone"` and candidates `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, `"Seton"`, the anagram set is `"tones"`, `"notes"`, `"Seton"`. +Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`. From 82e77cedafaac4c2ba00b3a5463f44a43697f6c7 Mon Sep 17 00:00:00 2001 From: qadzek <84473512+qadzek@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:33:35 +0100 Subject: [PATCH 762/826] Use correct double quotes (#3853) --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 5a38cf4c55..8c01c52481 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -256,7 +256,7 @@ Commands may vary by Linux distro, and whether a `fish` or `zsh` shell is used. Replace `` with the output of `python3 -c "import os, sys; print(os.path.dirname(sys.executable))"` ```bash -export PATH=”$PATH:” +export PATH="$PATH:" ``` [Code Quality: Tools and Best Practices]: https://realpython.com/python-code-quality/ From 550d1aba093959a19c818343624664856985f0d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:19:26 -0800 Subject: [PATCH 763/826] Bump actions/setup-python from 5.3.0 to 5.4.0 (#3858) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.3.0 to 5.4.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/0b93645e9fea7318ecaed2b359559ac225c90a2b...42375524e23c412d93fb67b49958b491fce71c38) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 8c9d9f7fb6..75154ccd5e 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 with: python-version: ${{ matrix.python-version }} From bd05215644a8bbba990fa430383cd7b779e46ef9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:19:55 -0800 Subject: [PATCH 764/826] Bump actions/stale from 9.0.0 to 9.1.0 (#3857) Bumps [actions/stale](https://github.com/actions/stale) from 9.0.0 to 9.1.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/28ca1036281a5e5922ead5184a1bbf96e5fc984e...5bef64f19d7facfb25b37b414482c7164d639639) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f526390311..03023bc033 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-22.04 steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 From 445e49f3a623fca83762b5f026f08099f65d4682 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 11 Feb 2025 18:06:11 -0800 Subject: [PATCH 765/826] [Mecha Munch Management]: Reformatted Test File & Pulled Data out into Its Own File (#3860) [no important files changed] * Reformatted test file to test file plus test data file. * Reverted stub to empty. * Updated config in hopes the tests pass. --- .../mecha-munch-management/.meta/config.json | 3 + .../dict_methods_test.py | 166 +------- .../dict_methods_test_data.py | 393 ++++++++++++++++++ 3 files changed, 419 insertions(+), 143 deletions(-) create mode 100644 exercises/concept/mecha-munch-management/dict_methods_test_data.py diff --git a/exercises/concept/mecha-munch-management/.meta/config.json b/exercises/concept/mecha-munch-management/.meta/config.json index f09d0f2953..b75803ad5a 100644 --- a/exercises/concept/mecha-munch-management/.meta/config.json +++ b/exercises/concept/mecha-munch-management/.meta/config.json @@ -14,6 +14,9 @@ ], "exemplar": [ ".meta/exemplar.py" + ], + "editor": [ + "dict_methods_test_data.py" ] }, "icon": "gross-store", diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 63fc1874a0..4d8dab865a 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -1,29 +1,29 @@ import unittest import pytest from collections import OrderedDict -from dict_methods import (add_item, - read_notes, - update_recipes, - sort_entries, - send_to_store, - update_store_inventory) - +from dict_methods import ( + add_item, + read_notes, + update_recipes, + sort_entries, + send_to_store, + update_store_inventory, +) + +from dict_methods_test_data import ( + add_item_data, + read_notes_data, + update_recipes_data, + sort_entries_data, + send_to_store_data, + update_store_inventory_data, +) class MechaMunchManagementTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_add_item(self): - input_data = [ - ({'Apple': 1, 'Banana': 4 }, ('Apple', 'Banana', 'Orange')), - ({'Orange': 1, 'Raspberry': 1, 'Blueberries': 10}, ['Raspberry', 'Blueberries', 'Raspberry']), - ({'Broccoli': 1, 'Banana': 1}, ('Broccoli', 'Kiwi', 'Kiwi', 'Kiwi', 'Melon', 'Apple', 'Banana', 'Banana')) - ] - - output_data = [{'Apple': 2, 'Banana': 5, 'Orange': 1}, - {'Orange': 1, 'Raspberry': 3, 'Blueberries': 11}, - {'Broccoli': 2, 'Banana': 3, 'Kiwi': 3, 'Melon': 1, 'Apple': 1}] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(add_item_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = add_item(input_data[0], input_data[1]) error_msg= (f'Called add_item({input_data[0]}, {input_data[1]}). ' @@ -34,13 +34,7 @@ def test_add_item(self): @pytest.mark.task(taskno=2) def test_read_notes(self): - input_data = [('Apple', "Banana"), ('Orange', 'Raspberry', 'Blueberries'), - ['Broccoli', 'Kiwi', 'Melon', 'Apple', 'Banana']] - - output_data = [{'Apple': 1, 'Banana': 1}, {'Orange': 1, 'Raspberry': 1, 'Blueberries': 1}, - {'Broccoli': 1, 'Kiwi': 1, 'Melon': 1, 'Apple': 1, 'Banana': 1}] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(read_notes_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = read_notes(input_data) error_msg = (f'Called read_notes({input_data}). ' @@ -51,36 +45,7 @@ def test_read_notes(self): @pytest.mark.task(taskno=3) def test_update_recipes(self): - input_data = [ - ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, - (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)), - - ({'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, - 'Blueberry Pie': {'Blueberries': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, - (('Blueberry Pie', {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}), - ('Apple Pie', {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}))), - - ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}, - 'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}}, - (('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}), - ('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}), - ('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}))) - ] - - output_data = [ - {'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, - 'Raspberry Pie': {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, - {'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, - 'Blueberry Pie': {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}}, - {'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}, - 'Pasta Primavera': {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}, - 'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}} - ] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(update_recipes_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = update_recipes(input_data[0], input_data[1]) error_msg = (f'Called update_recipes({input_data[0]}, {input_data[1]}). ' @@ -91,21 +56,7 @@ def test_update_recipes(self): @pytest.mark.task(taskno=4) def test_sort_entries(self): - input_data = [ - {'Banana': 4, 'Apple': 2, 'Orange': 1, 'Pear': 12}, - {'Apple': 3, 'Orange': 5, 'Banana': 1, 'Avocado': 2}, - {'Orange': 3, 'Banana': 2, 'Apple': 1}, - {'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4} - ] - - output_data = [ - {'Apple': 2, 'Banana': 4, 'Orange': 1, 'Pear': 12}, - {'Apple': 3, 'Avocado': 2, 'Banana': 1, 'Orange': 5}, - {'Apple': 1, 'Banana': 2, 'Orange': 3}, - {'Apple' : 2, 'Blueberries': 5, 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4, 'Raspberry': 2} - ] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(sort_entries_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expecred=expected): actual_result = sort_entries(input_data) error_msg = (f'Called sort_entries({input_data}). ' @@ -119,49 +70,7 @@ def test_sort_entries(self): @pytest.mark.task(taskno=5) def test_send_to_store(self): - input_data = [ - ({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, - {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], - 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), - - ({'Kiwi': 3, 'Juice': 5, 'Yoghurt': 2, 'Milk': 5}, - {'Kiwi': ['Aisle 6', False], 'Juice': ['Aisle 5', False], - 'Yoghurt': ['Aisle 2', True], 'Milk': ['Aisle 2', True]}), - - ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, - 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4}, - - {'Apple': ['Aisle 1', False], 'Raspberry': ['Aisle 6', False], - 'Blueberries': ['Aisle 6', False], 'Broccoli': ['Aisle 3', False], - 'Kiwi': ['Aisle 6', False], 'Melon': ['Aisle 6', False]}), - - ({'Orange': 1}, - {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], - 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), - - ({'Banana': 3, 'Apple': 2, 'Orange': 1}, - {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], - 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), - ] - - output_data = [ - {'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], - 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, - - {'Yoghurt': [2, 'Aisle 2', True], 'Milk': [5, 'Aisle 2', True], - 'Kiwi': [3, 'Aisle 6', False], 'Juice': [5, 'Aisle 5', False]}, - - {'Raspberry': [2, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], - 'Kiwi': [1, 'Aisle 6', False], 'Broccoli': [2, 'Aisle 3', False], - 'Blueberries': [5, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False]}, - - {'Orange': [1, 'Aisle 4', False]}, - - {'Orange': [1, 'Aisle 4', False], 'Banana': [3, 'Aisle 5', False], - 'Apple': [2, 'Aisle 4', False]}, - ] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(send_to_store_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = send_to_store(input_data[0], input_data[1]) error_msg = (f'Called send_to_store({input_data[0]}, {input_data[1]}). ' @@ -175,36 +84,7 @@ def test_send_to_store(self): @pytest.mark.task(taskno=6) def test_update_store_inventory(self): - input_data = [ - ({'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], - 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, - {'Banana': [15, 'Aisle 5', False], 'Apple': [12, 'Aisle 4', False], - 'Orange': [1, 'Aisle 4', False], 'Milk': [4, 'Aisle 2', True]}), - - ({'Kiwi': [3, 'Aisle 6', False]},{'Kiwi': [3, 'Aisle 6', False], 'Juice': [5, 'Aisle 5', False], - 'Yoghurt': [2, 'Aisle 2', True], 'Milk': [5, 'Aisle 2', True]}), - - ({'Kiwi': [1, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False], - 'Raspberry': [2, 'Aisle 6', False], 'Blueberries': [5, 'Aisle 6', False], - 'Broccoli': [1, 'Aisle 3', False]}, - {'Apple': [2, 'Aisle 1', False], 'Raspberry': [5, 'Aisle 6', False], - 'Blueberries': [10, 'Aisle 6', False], 'Broccoli': [4, 'Aisle 3', False], - 'Kiwi': [1, 'Aisle 6', False], 'Melon': [8, 'Aisle 6', False]}) - ] - - output_data = [ - {'Banana': [12, 'Aisle 5', False], 'Apple': [10, 'Aisle 4', False], - 'Orange': ['Out of Stock', 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True]}, - - {'Juice': [5, 'Aisle 5', False], 'Yoghurt': [2, 'Aisle 2', True], - 'Milk': [5, 'Aisle 2', True], 'Kiwi': ["Out of Stock", 'Aisle 6', False]}, - - {'Kiwi': ['Out of Stock', 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], - 'Apple': ['Out of Stock', 'Aisle 1', False], 'Raspberry': [3, 'Aisle 6', False], - 'Blueberries': [5, 'Aisle 6', False], 'Broccoli': [3, 'Aisle 3', False]} - ] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(update_store_inventory_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = update_store_inventory(input_data[0], input_data[1]) error_msg = (f'Called update_store_inventory({input_data[0]}, {input_data[1]}). ' diff --git a/exercises/concept/mecha-munch-management/dict_methods_test_data.py b/exercises/concept/mecha-munch-management/dict_methods_test_data.py new file mode 100644 index 0000000000..eea18cf541 --- /dev/null +++ b/exercises/concept/mecha-munch-management/dict_methods_test_data.py @@ -0,0 +1,393 @@ +##add_item test cases## +add_item_inputs = [ + ({"Apple": 1, "Banana": 4}, ("Apple", "Banana", "Orange")), + ( + {"Orange": 1, "Raspberry": 1, "Blueberries": 10}, + ["Raspberry", "Blueberries", "Raspberry"], + ), + ( + {"Broccoli": 1, "Banana": 1}, + ("Broccoli", "Kiwi", "Kiwi", "Kiwi", "Melon", "Apple", "Banana", "Banana"), + ), +] + +add_item_outputs = [ + {"Apple": 2, "Banana": 5, "Orange": 1}, + {"Orange": 1, "Raspberry": 3, "Blueberries": 11}, + {"Broccoli": 2, "Banana": 3, "Kiwi": 3, "Melon": 1, "Apple": 1}, +] + +add_item_data = zip(add_item_inputs, add_item_outputs) + + +##read_notes test cases## +read_notes_inputs = [ + ("Apple", "Banana"), + ("Orange", "Raspberry", "Blueberries"), + ["Broccoli", "Kiwi", "Melon", "Apple", "Banana"], +] + +read_notes_outputs = [ + {"Apple": 1, "Banana": 1}, + {"Orange": 1, "Raspberry": 1, "Blueberries": 1}, + {"Broccoli": 1, "Kiwi": 1, "Melon": 1, "Apple": 1, "Banana": 1}, +] + +read_notes_data = zip(read_notes_inputs, read_notes_outputs) + + +##update_recipes test cases## +update_recipes_inputs = [ + ( + { + "Banana Bread": { + "Banana": 1, + "Apple": 1, + "Walnuts": 1, + "Flour": 1, + "Eggs": 2, + "Butter": 1, + }, + "Raspberry Pie": { + "Raspberry": 1, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + }, + }, + ( + ( + "Banana Bread", + { + "Banana": 4, + "Walnuts": 2, + "Flour": 1, + "Butter": 1, + "Milk": 2, + "Eggs": 3, + }, + ), + ), + ), + ( + { + "Apple Pie": {"Apple": 1, "Pie Crust": 1, "Cream Custard": 1}, + "Blueberry Pie": {"Blueberries": 1, "Pie Crust": 1, "Cream Custard": 1}, + }, + ( + ("Blueberry Pie", {"Blueberries": 2, "Pie Crust": 1, "Cream Custard": 1}), + ("Apple Pie", {"Apple": 1, "Pie Crust": 1, "Cream Custard": 1}), + ), + ), + ( + { + "Banana Bread": { + "Banana": 1, + "Apple": 1, + "Walnuts": 1, + "Flour": 1, + "Eggs": 2, + "Butter": 1, + }, + "Raspberry Pie": { + "Raspberry": 1, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + }, + "Pasta Primavera": { + "Eggs": 1, + "Carrots": 1, + "Spinach": 2, + "Tomatoes": 3, + "Parmesan": 2, + "Milk": 1, + "Onion": 1, + }, + }, + ( + ( + "Raspberry Pie", + { + "Raspberry": 3, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + "Whipped Cream": 2, + }, + ), + ( + "Pasta Primavera", + { + "Eggs": 1, + "Mixed Veggies": 2, + "Parmesan": 2, + "Milk": 1, + "Spinach": 1, + "Bread Crumbs": 1, + }, + ), + ( + "Blueberry Crumble", + { + "Blueberries": 2, + "Whipped Creme": 2, + "Granola Topping": 2, + "Yogurt": 3, + }, + ), + ), + ), +] + +update_recipes_outputs = [ + { + "Banana Bread": { + "Banana": 4, + "Walnuts": 2, + "Flour": 1, + "Butter": 1, + "Milk": 2, + "Eggs": 3, + }, + "Raspberry Pie": { + "Raspberry": 1, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + }, + }, + { + "Apple Pie": {"Apple": 1, "Pie Crust": 1, "Cream Custard": 1}, + "Blueberry Pie": {"Blueberries": 2, "Pie Crust": 1, "Cream Custard": 1}, + }, + { + "Banana Bread": { + "Banana": 1, + "Apple": 1, + "Walnuts": 1, + "Flour": 1, + "Eggs": 2, + "Butter": 1, + }, + "Raspberry Pie": { + "Raspberry": 3, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + "Whipped Cream": 2, + }, + "Pasta Primavera": { + "Eggs": 1, + "Mixed Veggies": 2, + "Parmesan": 2, + "Milk": 1, + "Spinach": 1, + "Bread Crumbs": 1, + }, + "Blueberry Crumble": { + "Blueberries": 2, + "Whipped Creme": 2, + "Granola Topping": 2, + "Yogurt": 3, + }, + }, +] + +update_recipes_data = zip(update_recipes_inputs, update_recipes_outputs) + + +##sort_entries test cases## +sort_entries_inputs = [ + {"Banana": 4, "Apple": 2, "Orange": 1, "Pear": 12}, + {"Apple": 3, "Orange": 5, "Banana": 1, "Avocado": 2}, + {"Orange": 3, "Banana": 2, "Apple": 1}, + { + "Apple": 2, + "Raspberry": 2, + "Blueberries": 5, + "Broccoli": 2, + "Kiwi": 1, + "Melon": 4, + }, +] + +sort_entries_outputs = [ + {"Apple": 2, "Banana": 4, "Orange": 1, "Pear": 12}, + {"Apple": 3, "Avocado": 2, "Banana": 1, "Orange": 5}, + {"Apple": 1, "Banana": 2, "Orange": 3}, + { + "Apple": 2, + "Blueberries": 5, + "Broccoli": 2, + "Kiwi": 1, + "Melon": 4, + "Raspberry": 2, + }, +] + + +sort_entries_data = zip(sort_entries_inputs, sort_entries_outputs) + + +##send_to_store test cases## +send_to_store_inputs = [ + ( + {"Banana": 3, "Apple": 2, "Orange": 1, "Milk": 2}, + { + "Banana": ["Aisle 5", False], + "Apple": ["Aisle 4", False], + "Orange": ["Aisle 4", False], + "Milk": ["Aisle 2", True], + }, + ), + ( + {"Kiwi": 3, "Juice": 5, "Yoghurt": 2, "Milk": 5}, + { + "Kiwi": ["Aisle 6", False], + "Juice": ["Aisle 5", False], + "Yoghurt": ["Aisle 2", True], + "Milk": ["Aisle 2", True], + }, + ), + ( + { + "Apple": 2, + "Raspberry": 2, + "Blueberries": 5, + "Broccoli": 2, + "Kiwi": 1, + "Melon": 4, + }, + { + "Apple": ["Aisle 1", False], + "Raspberry": ["Aisle 6", False], + "Blueberries": ["Aisle 6", False], + "Broccoli": ["Aisle 3", False], + "Kiwi": ["Aisle 6", False], + "Melon": ["Aisle 6", False], + }, + ), + ( + {"Orange": 1}, + { + "Banana": ["Aisle 5", False], + "Apple": ["Aisle 4", False], + "Orange": ["Aisle 4", False], + "Milk": ["Aisle 2", True], + }, + ), + ( + {"Banana": 3, "Apple": 2, "Orange": 1}, + { + "Banana": ["Aisle 5", False], + "Apple": ["Aisle 4", False], + "Orange": ["Aisle 4", False], + "Milk": ["Aisle 2", True], + }, + ), +] + +send_to_store_outputs = [ + { + "Orange": [1, "Aisle 4", False], + "Milk": [2, "Aisle 2", True], + "Banana": [3, "Aisle 5", False], + "Apple": [2, "Aisle 4", False], + }, + { + "Yoghurt": [2, "Aisle 2", True], + "Milk": [5, "Aisle 2", True], + "Kiwi": [3, "Aisle 6", False], + "Juice": [5, "Aisle 5", False], + }, + { + "Raspberry": [2, "Aisle 6", False], + "Melon": [4, "Aisle 6", False], + "Kiwi": [1, "Aisle 6", False], + "Broccoli": [2, "Aisle 3", False], + "Blueberries": [5, "Aisle 6", False], + "Apple": [2, "Aisle 1", False], + }, + {"Orange": [1, "Aisle 4", False]}, + { + "Orange": [1, "Aisle 4", False], + "Banana": [3, "Aisle 5", False], + "Apple": [2, "Aisle 4", False], + }, +] + +send_to_store_data = zip(send_to_store_inputs, send_to_store_outputs) + + +##update_store_inventory test cases## +update_store_inventory_inputs = [ + ( + { + "Orange": [1, "Aisle 4", False], + "Milk": [2, "Aisle 2", True], + "Banana": [3, "Aisle 5", False], + "Apple": [2, "Aisle 4", False], + }, + { + "Banana": [15, "Aisle 5", False], + "Apple": [12, "Aisle 4", False], + "Orange": [1, "Aisle 4", False], + "Milk": [4, "Aisle 2", True], + }, + ), + ( + {"Kiwi": [3, "Aisle 6", False]}, + { + "Kiwi": [3, "Aisle 6", False], + "Juice": [5, "Aisle 5", False], + "Yoghurt": [2, "Aisle 2", True], + "Milk": [5, "Aisle 2", True], + }, + ), + ( + { + "Kiwi": [1, "Aisle 6", False], + "Melon": [4, "Aisle 6", False], + "Apple": [2, "Aisle 1", False], + "Raspberry": [2, "Aisle 6", False], + "Blueberries": [5, "Aisle 6", False], + "Broccoli": [1, "Aisle 3", False], + }, + { + "Apple": [2, "Aisle 1", False], + "Raspberry": [5, "Aisle 6", False], + "Blueberries": [10, "Aisle 6", False], + "Broccoli": [4, "Aisle 3", False], + "Kiwi": [1, "Aisle 6", False], + "Melon": [8, "Aisle 6", False], + }, + ), +] + +update_store_inventory_outputs = [ + { + "Banana": [12, "Aisle 5", False], + "Apple": [10, "Aisle 4", False], + "Orange": ["Out of Stock", "Aisle 4", False], + "Milk": [2, "Aisle 2", True], + }, + { + "Juice": [5, "Aisle 5", False], + "Yoghurt": [2, "Aisle 2", True], + "Milk": [5, "Aisle 2", True], + "Kiwi": ["Out of Stock", "Aisle 6", False], + }, + { + "Kiwi": ["Out of Stock", "Aisle 6", False], + "Melon": [4, "Aisle 6", False], + "Apple": ["Out of Stock", "Aisle 1", False], + "Raspberry": [3, "Aisle 6", False], + "Blueberries": [5, "Aisle 6", False], + "Broccoli": [3, "Aisle 3", False], + }, +] + +update_store_inventory_data = zip( + update_store_inventory_inputs, update_store_inventory_outputs +) From 1b095278cde317fe0c77c61e9d0761733d921214 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 12 Feb 2025 12:03:12 -0800 Subject: [PATCH 766/826] Added link to dict.keys for Mecha Munch Management hints file. (#3861) --- exercises/concept/mecha-munch-management/.docs/hints.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/concept/mecha-munch-management/.docs/hints.md b/exercises/concept/mecha-munch-management/.docs/hints.md index 3287768ff5..2d2f49e2cc 100644 --- a/exercises/concept/mecha-munch-management/.docs/hints.md +++ b/exercises/concept/mecha-munch-management/.docs/hints.md @@ -50,6 +50,7 @@ The dictionary section of the [official tutorial][dicts-docs] and the mapping ty [dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys [items]: https://docs.python.org/3/library/stdtypes.html#dict.items +[keys]: https://docs.python.org/3/library/stdtypes.html#dict.keys [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [mvp]: https://en.wikipedia.org/wiki/Minimum_viable_product [set-default]: https://docs.python.org/3/library/stdtypes.html#dict.setdefault From 6352edb4530657217532adfacaf89fef3e4a61e1 Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Thu, 13 Feb 2025 23:34:50 +0000 Subject: [PATCH 767/826] Update CONTRIBUTING.md (#3863) --- CONTRIBUTING.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc278b027e..3c9c33c468 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,23 +8,30 @@   [![Exercism_III](https://img.shields.io/badge/PAUSED-C73D4E?labelColor=3D454D&label=Contributions)](https://exercism.org/blog/freeing-our-maintainers)   [![Build Status](https://github.com/exercism/python/workflows/Exercises%20check/badge.svg)](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22) -
+--- +--- -Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 +### We are not accepting community contributions at this time.
-
-We 💛 💙   our community. -**`But our maintainers are not accepting community contributions at this time.`** -Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details. +We're grateful for your interested in helping improve the Python track. but our maintainers are **not accepting community contributions at this time.** +If you would like to discuss possible future changes, please open a [thread on the forum](https://forum.exercism.org/). + +This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details.
+ +--- +--- +
+Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 + **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. From c99a16e8378f984b9c0242c105a2c0fdd987504f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 13 Feb 2025 20:45:19 -0800 Subject: [PATCH 768/826] [Bank Account]: Added Explicit Instruction Append for not Doing Concurrency Tests. (#3864) * Added explicit instruction append for not doing concurrency tests. * Apply suggestions from code review Co-authored-by: Isaac Good --------- Co-authored-by: Isaac Good --- .../bank-account/.docs/instructions.append.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/exercises/practice/bank-account/.docs/instructions.append.md b/exercises/practice/bank-account/.docs/instructions.append.md index 6204bee7dd..f49101c319 100644 --- a/exercises/practice/bank-account/.docs/instructions.append.md +++ b/exercises/practice/bank-account/.docs/instructions.append.md @@ -1,5 +1,22 @@ # Instructions append +````exercim/note +Python doesn't support "true" concurrency due to the [Global Interpreter Lock][GIL]. +While work is ongoing to create support for [free-threading in Python][free-threading], it is still experimental. +Current standard library solutions such as [multiprocessing][multiprocessing-module] and [threading][threading-module] are difficult to implement with the current track tooling. + + +As a result, the concurrency requirement has been set aside for this exercise. +Account operations are sequential on a single thread, and no concurrency or "race condition" tests are run. + +[GIL]: https://realpython.com/python-gil/ +[free-threading]: https://docs.python.org/3/howto/free-threading-python.html +[threading-module]: https://docs.python.org/3/library/threading.html#module-threading +[multiprocessing-module]: https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes +```` + +
+ ## Exception messages Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. From 664f1dbd91fd7e6c4d5e0c6811f32c221ac01e0a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Feb 2025 11:56:08 -0800 Subject: [PATCH 769/826] [Sieve]: Update content.md for Comprehension Approach (#3865) Added language about the disallowed `%` operator to the Comprehensions approach. --- exercises/practice/sieve/.approaches/comprehensions/content.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/sieve/.approaches/comprehensions/content.md b/exercises/practice/sieve/.approaches/comprehensions/content.md index 664ea32c11..2f0d778bd3 100644 --- a/exercises/practice/sieve/.approaches/comprehensions/content.md +++ b/exercises/practice/sieve/.approaches/comprehensions/content.md @@ -27,7 +27,8 @@ def primes(limit): if all(number % divisor != 0 for divisor in range(2, number))] ``` -This second example using a `list-comprehension` with `all()` is certainly concise and _relatively_ readable, but the performance is again quite poor. +This second example using a `list-comprehension` with `all()` is certainly concise and _relatively_ readable, but it uses **`%`** (_which the instructions ask you not to use_) and the performance is again quite poor. + This is not quite a fully nested loop (_there is a short-circuit when `all()` evaluates to `False`_), but it is by no means "performant". In this case, scaling with input size is intermediate between linear and quadratic, so not quite as bad as the first example. From 889cf840544cb77c350161cb82ce1fce5416109e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Feb 2025 16:24:01 -0800 Subject: [PATCH 770/826] Added admonition for not accepting community contributions and updated Python versions. (#3866) --- CONTRIBUTING.md | 70 ++++++++++++++++++++----------------------------- README.md | 28 +++++++++++++++----- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c9c33c468..d9c30d85e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,29 +4,26 @@

Contributing

                          [![Discourse topics](https://img.shields.io/discourse/topics?color=8A08E6&label=Connect%20&labelColor=FFDF58&logo=Discourse&logoColor=8A08E6&server=https%3A%2F%2Fforum.exercism.org&style=social)](https://forum.exercism.org) -  [![Exercism_II](https://img.shields.io/badge/Exercism--Built-9101FF?logo=python&logoColor=FFDF58&labelColor=3D7AAB&label=Python%203.10%20Powered)](https://exercism.org) +  [![Exercism_II](https://img.shields.io/badge/Exercism--Built-9101FF?logo=python&logoColor=FFDF58&labelColor=3D7AAB&label=Python%203.11%20Powered)](https://exercism.org)   [![Exercism_III](https://img.shields.io/badge/PAUSED-C73D4E?labelColor=3D454D&label=Contributions)](https://exercism.org/blog/freeing-our-maintainers)   [![Build Status](https://github.com/exercism/python/workflows/Exercises%20check/badge.svg)](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22) ---- ---- -### We are not accepting community contributions at this time. +> [!IMPORTANT] +>

We are not accepting community contributions at this time.

+> +> +> +> +> We love our community. We're grateful you are interested in improving the Python track. +> But our maintainers are **not accepting community contributions at this time.** +> If you would like to discuss possible future changes, please open a [thread on the forum](https://forum.exercism.org/). +> +> This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details. +> +> +>
- - - - -We're grateful for your interested in helping improve the Python track. but our maintainers are **not accepting community contributions at this time.** -If you would like to discuss possible future changes, please open a [thread on the forum](https://forum.exercism.org/). - -This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details. - - -
- ---- ----
@@ -35,7 +32,7 @@ Hi.  👋🏽  👋  **We are happy you are here.**  🎉&nb **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. -🌟   Track exercises support Python `3.7` - `3.11.2`. +🌟   Track exercises support Python `3.7` - `3.11.5`. Exceptions to this support are noted where they occur. 🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`. @@ -50,20 +47,25 @@ Practice exercises are open-ended, and can be used to practice concepts learned, It is not uncommon to discover typos, confusing directions, or incorrect implementations of certain tests or code examples. Or you might have a great suggestion for a hint to aid students ( 💙  ), see optimizations for exemplar or test code, find missing test cases to add, or want to correct factual and/or logical errors. Or maybe you have a great idea 💡 for an exercise or feature ( 💙 ). _Our track is always a work in progress!_ 🌟🌟 -While contributions are paused, we ask that you [`open a thread in our community forum`](https://forum.exercism.org) to let us know what you have found/suggest. +While contributions are paused, we ask that you [**open a thread in our community forum**](https://forum.exercism.org) to let us know what you have found/suggest.
## 🚧 **Did you write a patch that fixes a bug?** -**`Our maintainers are not accepting community contributions at this time.`** -Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details. +Our maintainers are not accepting community contributions at this time. +
+Until the pause on contributions ends, all PRs from the larger community will be **automatically closed** with a note. +We ask that you [**open a thread in our community forum**](https://forum.exercism.org) to discuss any potential changes. Changes may or may not be approved, depending on the forum discussion. -Once the pause ends, we will **happily** consider your PR. -Until that time, all PRs from the larger community will be **automatically closed** with a note. +Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for additional details. +
-We're leaving the general contributing docs below for our long-term collaborators and maintainers. +We're leaving the track contributing docs below for our long-term collaborators and maintainers. +
+
+ Python Track Contributing Docs

In General

@@ -376,45 +378,32 @@ configlet generate --spec-path path/to/problem/specifications -- configlet generate --spec-path path/to/problem/specifications ``` +
+
[.flake8]: https://github.com/exercism/python/blob/main/.flake8 [.style.yapf]: https://github.com/exercism/python/blob/main/.style.yapf [american-english]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md -[being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member [card-games-testfile]: https://github.com/exercism/python/blob/main/exercises/concept/card-games/lists_test.py [cater-waiter]: https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter [concept-exercise-anatomy]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md -[concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md [config-json]: https://github.com/exercism/javascript/blob/main/config.json -[config-json]: https://github.com/exercism/python/blob/main/config.json -[configlet-general]: https://github.com/exercism/configlet [configlet-lint]: https://github.com/exercism/configlet#configlet-lint [configlet]: https://github.com/exercism/docs/blob/main/building/configlet/generating-documents.md [distinguishing-test-iterations]: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests [enumerate]: https://docs.python.org/3/library/functions.html#enumerate [eol]: https://en.wikipedia.org/wiki/Newline [exercise-config-json]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md#full-example -[exercise-presentation]: https://github.com/exercism/docs/blob/main/building/tracks/presentation.md -[exercism-admins]: https://github.com/exercism/docs/blob/main/community/administrators.md -[exercism-code-of-conduct]: https://exercism.org/docs/using/legal/code-of-conduct -[exercism-concepts]: https://github.com/exercism/docs/blob/main/building/tracks/concepts.md -[exercism-contributors]: https://github.com/exercism/docs/blob/main/community/contributors.md [exercism-internal-linking]: https://github.com/exercism/docs/blob/main/building/markdown/internal-linking.md [exercism-markdown-specification]: https://github.com/exercism/docs/blob/main/building/markdown/markdown.md [exercism-markdown-widgets]: https://github.com/exercism/docs/blob/main/building/markdown/widgets.md -[exercism-mentors]: https://github.com/exercism/docs/tree/main/mentoring -[exercism-tasks]: https://exercism.org/docs/building/product/tasks -[exercism-track-maintainers]: https://github.com/exercism/docs/blob/main/community/maintainers.md -[exercism-track-structure]: https://github.com/exercism/docs/tree/main/building/tracks [exercism-website]: https://exercism.org/ -[exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md [flake8-noqa]: https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html#in-line-ignoring-errors [flake8]: http://flake8.pycqa.org/ [google-coding-style]: https://google.github.io/styleguide/pyguide.html [guidos-gorgeous-lasagna-testfile]: https://github.com/exercism/python/blob/main/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py -[help-wanted]: https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 [implicit-line-joining]: https://google.github.io/styleguide/pyguide.html#32-line-length [markdown-language]: https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf [open-an-issue]: https://github.com/exercism/python/issues/new/choose @@ -436,5 +425,4 @@ configlet generate --spec-path path/to/problem/specifications [the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md [unittest]: https://docs.python.org/3/library/unittest.html#unittest.TestCase [version-tagged-language-features]: https://docs.python.org/3/library/stdtypes.html#dict.popitem -[website-contributing-section]: https://exercism.org/docs/building [yapf]: https://github.com/google/yapf diff --git a/README.md b/README.md index d8ec664458..1d2ae6861f 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,27 @@
-Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 +> [!IMPORTANT] +>

We are not accepting community contributions at this time.

+> +> +> +> +> We love our community. We're grateful you are interested in improving the Python track. +> But our maintainers are **not accepting community contributions at this time.** +> If you would like to suggest a change or discuss an issue, please open a [thread on the forum](https://forum.exercism.org/). +> +> This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details. +> +> +>

+Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 + +

+ **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. @@ -43,15 +60,15 @@ It might also be helpful to look at [Being a Good Community Member][being-a-good
-We 💛 💙   our community. -**`But our maintainers are not accepting community contributions at this time.`** +We 💛 💙 our community. +**But our maintainers are not accepting community contributions at this time.** Please read this [community blog post][freeing-maintainers] for details.
Here to suggest a new feature or new exercise?? **Hooray!**  🎉   -We'd love if you did that via our [Exercism Community Forum](https://forum.exercism.org/). +We'd love if you did that via our [Community Forum](https://forum.exercism.org/). Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence]. _Thoughtful suggestions will likely result in faster & more enthusiastic responses from volunteers._ @@ -67,7 +84,7 @@ _Thoughtful suggestions will likely result in faster & more enthusiastic respons ## Python Software and Documentation -**Copyright © 2001-2023 Python Software Foundation. All rights reserved.** +**Copyright © 2001-2025 Python Software Foundation. All rights reserved.** Python software and documentation are licensed under the [PSF License Agreement][psf-license]. @@ -99,7 +116,6 @@ This repository uses the [MIT License](/LICENSE). [exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md [freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers [practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md -[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md [psf-license]: https://docs.python.org/3/license.html#psf-license [python-syllabus]: https://exercism.org/tracks/python/concepts [suggesting-improvements]: https://github.com/exercism/docs/blob/main/community/good-member/suggesting-exercise-improvements.md From 7a12eeb1fc0ec78713f474978f5f4e6deb656013 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Feb 2025 16:38:54 -0800 Subject: [PATCH 771/826] Update README.md (#3867) Formatting shenanigans, due to the way this displays on the Repo. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d2ae6861f..f3d083aab4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ > > We love our community. We're grateful you are interested in improving the Python track. > But our maintainers are **not accepting community contributions at this time.** -> If you would like to suggest a change or discuss an issue, please open a [thread on the forum](https://forum.exercism.org/). +> If you would like to suggest a change / discuss an issue, please open a [thread on the forum](https://forum.exercism.org/). > > This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details. > From 6ffac57e57bcffe239f019d2d518311d3c821c37 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Feb 2025 17:40:31 -0800 Subject: [PATCH 772/826] Removed President test case and regnerated test file. Corrected formatting in approaches introduction. (#3868) [no important files changed] --- exercises/practice/wordy/.approaches/introduction.md | 7 +++++-- exercises/practice/wordy/.meta/tests.toml | 1 + exercises/practice/wordy/wordy_test.py | 8 +------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index c6d7ed8927..cc5e8031d9 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -54,8 +54,11 @@ For question cleaning, [`str.removeprefix`][removeprefix] and 'Supercalifragilistic' -#The two methods can be chained to remove both a suffix and prefix in one line. ->>> 'Supercalifragilisticexpialidocious'.removesuffix('expialidocious').removeprefix('Super') +#The two methods can be chained to remove both a suffix and prefix in "one line". +#The line has been broken up here for better display. +>>> ('Supercalifragilisticexpialidocious' + .removesuffix('expialidocious') + .removeprefix('Super')) 'califragilistic' ``` diff --git a/exercises/practice/wordy/.meta/tests.toml b/exercises/practice/wordy/.meta/tests.toml index 912d576009..1d0f9d92f0 100644 --- a/exercises/practice/wordy/.meta/tests.toml +++ b/exercises/practice/wordy/.meta/tests.toml @@ -52,6 +52,7 @@ description = "unknown operation" [8a7e85a8-9e7b-4d46-868f-6d759f4648f8] description = "Non math question" +include = false [42d78b5f-dbd7-4cdb-8b30-00f794bb24cf] description = "reject problem missing an operand" diff --git a/exercises/practice/wordy/wordy_test.py b/exercises/practice/wordy/wordy_test.py index ffcaf49aed..e9d01e9ef3 100644 --- a/exercises/practice/wordy/wordy_test.py +++ b/exercises/practice/wordy/wordy_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-02-15 import unittest @@ -61,12 +61,6 @@ def test_unknown_operation(self): self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "unknown operation") - def test_non_math_question(self): - with self.assertRaises(ValueError) as err: - answer("Who is the President of the United States?") - self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "unknown operation") - def test_reject_problem_missing_an_operand(self): with self.assertRaises(ValueError) as err: answer("What is 1 plus?") From 93744302aa5f36eabcbafd3402f5ca766f71a2bc Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Feb 2025 18:32:13 -0800 Subject: [PATCH 773/826] [Reverse String]: Add Approaches, Performance, and Unicode Articles (#3609) * Added approaches and articles for reverse-string. * Added code snippets for each approach. * remaned files to silence configlet. * renamed article snippets to .md to satisfy configlet. * Apply suggestions from code review Therefor != Therefore. TIL. Added missing link, so should no longer be broken. * Added more on the downfalls and gotchas of unicode. * Wrapping this up for now. * Deleted the Unicode directory for now. --- .../additional-approaches/content.md | 159 ++++++++++++++++ .../additional-approaches/snippet.txt | 8 + .../backward-iteration-with-range/content.md | 78 ++++++++ .../backward-iteration-with-range/snippet.txt | 5 + .../built-in-list-reverse/content.md | 83 +++++++++ .../built-in-list-reverse/snippet.txt | 5 + .../.approaches/built-in-reversed/content.md | 54 ++++++ .../.approaches/built-in-reversed/snippet.txt | 2 + .../reverse-string/.approaches/config.json | 57 ++++++ .../.approaches/introduction.md | 171 ++++++++++++++++++ .../iteration-and-concatenation/content.md | 110 +++++++++++ .../iteration-and-concatenation/snippet.txt | 5 + .../.approaches/list-and-join/content.md | 148 +++++++++++++++ .../.approaches/list-and-join/snippet.txt | 8 + .../.approaches/sequence-slicing/content.md | 48 +++++ .../.approaches/sequence-slicing/snippet.txt | 2 + .../reverse-string/.articles/config.json | 11 ++ .../.articles/performance/code/Benchmark.py | 127 +++++++++++++ .../.articles/performance/content.md | 60 ++++++ .../.articles/performance/snippet.md | 8 + 20 files changed, 1149 insertions(+) create mode 100644 exercises/practice/reverse-string/.approaches/additional-approaches/content.md create mode 100644 exercises/practice/reverse-string/.approaches/additional-approaches/snippet.txt create mode 100644 exercises/practice/reverse-string/.approaches/backward-iteration-with-range/content.md create mode 100644 exercises/practice/reverse-string/.approaches/backward-iteration-with-range/snippet.txt create mode 100644 exercises/practice/reverse-string/.approaches/built-in-list-reverse/content.md create mode 100644 exercises/practice/reverse-string/.approaches/built-in-list-reverse/snippet.txt create mode 100644 exercises/practice/reverse-string/.approaches/built-in-reversed/content.md create mode 100644 exercises/practice/reverse-string/.approaches/built-in-reversed/snippet.txt create mode 100644 exercises/practice/reverse-string/.approaches/config.json create mode 100644 exercises/practice/reverse-string/.approaches/introduction.md create mode 100644 exercises/practice/reverse-string/.approaches/iteration-and-concatenation/content.md create mode 100644 exercises/practice/reverse-string/.approaches/iteration-and-concatenation/snippet.txt create mode 100644 exercises/practice/reverse-string/.approaches/list-and-join/content.md create mode 100644 exercises/practice/reverse-string/.approaches/list-and-join/snippet.txt create mode 100644 exercises/practice/reverse-string/.approaches/sequence-slicing/content.md create mode 100644 exercises/practice/reverse-string/.approaches/sequence-slicing/snippet.txt create mode 100644 exercises/practice/reverse-string/.articles/config.json create mode 100644 exercises/practice/reverse-string/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/reverse-string/.articles/performance/content.md create mode 100644 exercises/practice/reverse-string/.articles/performance/snippet.md diff --git a/exercises/practice/reverse-string/.approaches/additional-approaches/content.md b/exercises/practice/reverse-string/.approaches/additional-approaches/content.md new file mode 100644 index 0000000000..1ff735608c --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/additional-approaches/content.md @@ -0,0 +1,159 @@ +# Additional Approaches that are Further Afield + +Below are some interesting strategies that are distinct from the canonical approaches that have already been discussed. +While they do not offer particular performance boosts over the canonical approaches (_and some offer very large penalties_), they do explore interesting corners of Python. + + +## Convert the Input to a UTF-8 bytearray and use a Sliding Window to Reverse + + +```python +def reverse(text): + + # Create bytearrays for input and output. + given, output = bytearray(text.encode("utf-8")), bytearray(len(text)) + index = 0 + LENGTH_MASK = 0xE0 # this is 0b11110000 (binary) or 224 (decimal) + + # Loop through the input bytearray. + while index < len(given): + + #Either the len is 1 or it is calculated by counting the bits after masking. + seq_len = (not(given[index] >> 7) or + (given[index] & LENGTH_MASK).bit_count()) + + #Calculate the index start. + location = index + seq_len +1 + + #Prepend the byte segment to the output bytearray + output[-location:-index or None] = given[index:index + seq_len] + + #Increment the index count or slide the 'window'. + index += seq_len + + #Decode output to UTF-8 string and return. + return output.decode("utf-8") + +``` + +This strategy encodes the string into a UTF-8 [`bytearray`][bytearray]. +It then uses a `while` loop to iterate through the text, calculating the length of a sequence (or 'window') to slice from 'given' and prepend to 'output'. + The 'index' counter is then incremented by the length of the 'window'. + Once the 'index' is greater than the length of 'given', the 'output' bytearray is decoded into a UTF-8 string and returned. + This is (_almost_) the same set of operations as described in the code below, but operating on bytes in a bytearray, as opposed to text/codepoints in a `list` - although this strategy does not use `list.pop()` (_bytearray objects do not have a pop method_). + + This uses `O(n)` space for the output array. +It incurs additional runtime overhead by _prepending_ to the output array, which is an expensive operation that forces many repeated shifts. +Encoding to bytes and decoding to codepoints further slow this approach. + + +## Convert the Input to a list and use a While Loop to Pop and Append to a Second List + + +```python +def reverse(text): + codepoints, stniopedoc = list(text), [] + + while codepoints: + stniopedoc.append(codepoints.pop()) + + return ''.join(stniopedoc) +``` + +This strategy uses two lists. +One `list` for the codepoints in the text, and one to hold the codepoints in reverse order. +First, the input text is turned into a the 'codepoints' `list`, and iterated over. +Each codepoint is `pop()`ped from 'codepoints' and appended to the 'stniopedoc' `list`. +Finally, 'stniopedoc' is joined via `str.join()` to create the reversed string. + +While this is a straightforward and readable approach, it creates both memory and performance overhead, due to the creation of the lists and the use of `join()`. +This is much faster than the bytearray strategy or using string concatenation, but is still almost slower than the slicing strategy. +It also takes up `O(n)` auxiliary space with the stniopedoc list. + + + +## Using Recursion Instead of a Loop + + +```python +def reverse(text): + if len(text) == 0: + return text + else: + return reverse(text[1:]) + text[0] +``` + +This strategy uses a slice to copy all but the leftmost part of the string, concatenating the codepoint at the first index to the end. +The function then calls itself with the (now shorter) text slice. +This slice + concatenation process continues until the `len()` is 0, and the reversed text is returned up the call stack. +This is the same as iterating over the string backward in a `loop`, appending each codepoint to a new string, and has identical time complexity. +It also uses O(n) space, with the space being successive calls on the call stack. + +Because each recursive call is placed on the stack and Python limits recursive calls to a max of 1000, this code produces a `maximum recursion depth exceeded` error for any string longer than ~999 characters. + + +## Using `map()` and `lambbda` with `Join()` Instead of a Loop + +```python +def reverse(text): + return "".join(list(map(lambda x: text[(-x-1)], range(len(text))))) +``` + +This variation uses the built-in `map()` and a `lambda` to iterate over the string backward, constructing a `list`. +The `list` is then fed to `str.join()`, which unpacks it and turns it into a string. +This is a very non-performant way to walk the string backwards, and also incurs extra overhead due to the unneeded construction of an intermediary `list`. + +`map()` can instead be directly fed to `join()`, which improves performance to `O(n)`: + +```python +def reverse(text): + return "".join(map(lambda x: text[(-x-1)], range(len(text)))) +``` + + +## Using a `lambda` that returns a Reverse Sequence Slice + + +```python +reverse = lambda text: text[::-1] +``` + + +This strategy assigns the name "reverse" to a `lambda` that produces a reverse slice of the string. +This looks quite clever and is shorter than a "traditional" function, but it is far from obvious that this line defines a callable named "reverse" that returns a reversed string. +While this code compiles to the same function definition as the first approach article, it is not clear to many programmers who might read through this code that they could call `reverse('some_string')` the way they could call other functions. + + +This has the added disadvantage of creating troubleshooting issues since any errors will be attributed to `lambda` in the stack trace and not associated with an explicit function named `reverse`. +Help calls and `__repr__` calls are similarly affected. +This is not the intended use of `lambdas` (_which are for unnamed or anonymous functions_), nor does it confer any sort of performance boost over other methods, but _does_ create readability issues with anyone unfamiliar with `lambda` syntax and compilation. + + +## Timings vs Reverse Slice + + +As a (very) rough comparison, below is a timing table for these functions vs the canonical reverse slice: + + +| **string lengths >>>>** | Str Len: 5 | Str Len: 11 | Str Len: 22 | Str Len: 52 | Str Len: 68 | Str Len: 86 | Str Len: 142 | Str Len: 1420 | Str Len: 14200 | Str Len: 142000 | +|------------------------- |------------ |------------- |------------- |------------- |------------- |------------- |-------------- |--------------- |---------------- |----------------- | +| reverse slice | 1.66e-07 | 1.75e-07 | 1.79e-07 | 2.03e-07 | 2.22e-07 | 2.38e-07 | 3.63e-07 | 1.44e-06 | 1.17e-05 | 1.16e-04 | +| reverse lambda | 1.68e-07 | 1.72e-07 | 1.85e-07 | 2.03e-07 | 2.44e-07 | 2.35e-07 | 3.65e-07 | 1.47e-06 | 1.25e-05 | 1.18e-04 | +| reverse dual lists | 9.17e-07 | 1.56e-06 | 2.70e-06 | 5.69e-06 | 8.30e-06 | 1.07e-05 | 1.80e-05 | 1.48e-04 | 1.50e-03 | 1.53e-02 | +| reverse recursive | 8.74e-07 | 1.90e-06 | 4.02e-06 | 8.97e-06 | 1.24e-05 | 1.47e-05 | 3.34e-05 | --- | --- | --- | +| reverse bytes | 1.92e-06 | 3.82e-06 | 7.36e-06 | 1.65e-05 | 2.17e-05 | 2.71e-05 | 4.47e-05 | 5.17e-04 | 6.10e-03 | 2.16e-01 | + + +As you can see, the reverse using two lists and the reverse using a bytearray are orders of magnitude slower than using a reverse slice. +For the largest inputs measured, the dual list solution was almost 55x slower, and the bytearray solution was almost 1800x slower. +Timings for strings over 142 characters could not be run for the recursive strategy, due to Python's 1000 call recursion limit. + + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + +[bytearray]: https://docs.python.org/3/library/stdtypes.html#bytearray +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ diff --git a/exercises/practice/reverse-string/.approaches/additional-approaches/snippet.txt b/exercises/practice/reverse-string/.approaches/additional-approaches/snippet.txt new file mode 100644 index 0000000000..9bb10135a0 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/additional-approaches/snippet.txt @@ -0,0 +1,8 @@ + given, output = bytearray(text.encode("utf-8")), bytearray(len(given)) + index, LENGTH_MASK = 0, 0xE0 # 0b11110000 or 224 + while index < len(given): + seq_len = not(given[index] >> 7) or (given[index] & LENGTH_MASK).bit_count() + location = index + seq_len +1 + output[-location:-index or None] = given[index:index + seq_len] + index += seq_len + return output.decode("utf-8") \ No newline at end of file diff --git a/exercises/practice/reverse-string/.approaches/backward-iteration-with-range/content.md b/exercises/practice/reverse-string/.approaches/backward-iteration-with-range/content.md new file mode 100644 index 0000000000..7b1ddd5b77 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/backward-iteration-with-range/content.md @@ -0,0 +1,78 @@ +## Backward Iteration with Range + + +```python +def reverse(text): + output = "" + for index in range(len(text) - 1, -1, -1): #For 'Robot', this is 4 (start) 0 (stop), iterating (4,3,2,1,0) + output += text[index] + return output +``` + + +These variations all use the built-in [`range()`][range] object to iterate over the input text from right --> left, adding each codepoint to the output string. +This is the same as iterating over the text backward using one or more `index` variables, but incurs slightly less overhead by substituting `range()` for them. +Note that the code above also avoids _prepending_ to the output string. + +For very long strings, this code will still degrade to `O(n**2)` performance, due to the use of string concatenation. +Using `''.join()` here can avoid heavy concatenation penalty as strings grow longer and the CPython string append optimization mentioned in the [iteration and concatenation][approach-iteration-and-concatenation] approach breaks down. + + +## Variation #1: Forward Iteration in Range, Negative Index + + +```python +def reverse(text): + output = '' + + for index in range(1, len(text) + 1): + output += text[-index] + return output +``` + + +This version iterates left --> right using a positive `range()` and then _prepends to the string_ by using a negative index for the codepoint. +This has the same faults as variation #1, with the added cost of prepending via concatenation. + + +## Variation #2: Feed Range and the Index into Join() + +```python +def reverse(text): + return "".join(text[index] for index in range(len(text)-1, -1, -1)) + ``` + + This version omits the intermediary output string, and uses `"".join()` directly in the return. + Within the `join()` call, `range()` is used with a negative step to iterate over the input text backward. + + This strategy avoids the penalties of string concatenation with an intermediary string. + It is still `O(n)` in time complexity, and is slower than reverse indexing due to the calls to `join()`, `len()` and `range()`, and the creation of the generator expression. + + Because of the aforementioned string append optimization in CPython, this approach will benchmark slower for strings under length 1000, but becomes more and more efficient as the length of the string grows. + Since the CPython optimization is not stable nor transferable to other versions of Python, using `join()` by default is recommended in any situation where the string concatenation is not strictly repetition and length constrained. + + +## Timings vs Reverse Slice + + + As a (very) rough comparison, below is a timing table for these functions vs the canonical reverse slice: + + + +| **string length >>>>** | 5 | 11 | 22 | 52 | 66 | 86 | 142 | 1420 | 14200 | 142000 | +|------------------------ |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| reverse slice | 1.68e-07 | 1.74e-07 | 1.83e-07 | 2.07e-07 | 2.14e-07 | 2.29e-07 | 3.51e-07 | 1.50e-06 | 1.19e-05 | 1.17e-04 | +| reverse negative range | 5.89e-07 | 9.93e-07 | 1.78e-06 | 3.69e-06 | 4.71e-06 | 5.83e-06 | 9.61e-06 | 1.39e-04 | 1.46e-03 | 1.81e-02 | +| reverse positive range | 6.20e-07 | 1.14e-06 | 2.23e-06 | 4.54e-06 | 5.74e-06 | 7.38e-06 | 1.20e-05 | 1.70e-04 | 1.75e-03 | 2.07e-02 | +| reverse range and join | 8.90e-07 | 1.31e-06 | 2.14e-06 | 4.15e-06 | 5.22e-06 | 6.57e-06 | 1.06e-05 | 1.05e-04 | 1.04e-03 | 1.07e-02 | + + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + +[approach-iteration-and-concatenation]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/iteration-and-concatenation +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[range]: https://docs.python.org/3/library/stdtypes.html#range +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface diff --git a/exercises/practice/reverse-string/.approaches/backward-iteration-with-range/snippet.txt b/exercises/practice/reverse-string/.approaches/backward-iteration-with-range/snippet.txt new file mode 100644 index 0000000000..cdb261d85a --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/backward-iteration-with-range/snippet.txt @@ -0,0 +1,5 @@ +def reverse(text): + new_word = "" + for index in range(len(text) - 1, -1, -1): + new_word += text[index] + return new_word diff --git a/exercises/practice/reverse-string/.approaches/built-in-list-reverse/content.md b/exercises/practice/reverse-string/.approaches/built-in-list-reverse/content.md new file mode 100644 index 0000000000..b195d099a5 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/built-in-list-reverse/content.md @@ -0,0 +1,83 @@ +# Make the Input Text a List and Use list.reverse() to Reverse In-Place + + +```python +def reverse(text): + output = list(text) + output.reverse() + + return ''.join(output) +``` + +These approaches start with turning the text into a `list` of codepoints. +Rather than use a loop + append to then reverse the text, the [`list.reverse()`][list-reverse-method] method is used to perform an in-place reversal. +`join()` is then used to turn the list into a string. + +This takes `O(n)` time complexity because `list.reverse()` & `join()` iterate through the entire `list`. +It uses `O(n)` space for the output `list`. + +`list.reverse()` cannot be fed to `join()` here because it returns `None` as opposed to returning the `list`. +Because `list.reverse()` **mutates the list**, it is not advisable in situations where you want to preserve the original `list` of codepoints. + + +## Variation #1: Keep a Copy of the Original Ordering of Codepoints + + +```python +def reverse(text): + codepoints, output = list(text), list(text) + output.reverse() + return ''.join(output) +``` + +This variation is essentially the same as the solution above, but makes a codepoints list to keep the original codepoint ordering of the input text. +This does add some time and space overhead. + + +## Variation #2: Iterate Through the String and Append to Create List Before Reversing + + +```python +def reverse(text): + output = [] + + for item in text: + output.append(item) + + output.reverse() + + return ''.join(output) +``` + +This variation declares output as an empty literal, then loops through the codepoints of the input text and appends them to output. +`list.reverse()` is then called to reverse output in place. + Finally, output is joined into a string via `str.join()`. +Using this method is the same as calling the `list` constructor directly on the input text (_`list(text)`_), which will iterate through it automatically. + Calling the constructor is also quite a bit faster than using a "written out" `for-loop`. + + +## Timings vs Reverse Slice + + +As a (very) rough comparison, below is a timing table for these functions vs the canonical reverse slice: + +As you can see, using `list.reverse()` after converting the input text to a list is much slower than using a reverse slice. +Iterating in a loop to create the output list also adds even more time. + + +| **string lengths >>>>** | 5 | 11 | 22 | 52 | 66 | 86 | 142 | 1420 | 14200 | 142000 | +|------------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| reverse slice | 1.70e-07 | 1.74e-07 | 1.00e-07 | 2.06e-07 | 2.20e-07 | 2.39e-07 | 3.59e-07 | 1.47e-06 | 1.22e-05 | 1.20e-04 | +| reverse reverse method | 3.28e-07 | 2.00e-07 | 5.39e-07 | 8.96e-07 | 1.35e-06 | 1.55e-06 | 2.31e-06 | 2.01e-05 | 1.93e-04 | 1.94e-03 | +| reverse iterate list | 4.74e-07 | 7.60e-07 | 1.25e-06 | 2.75e-06 | 3.53e-06 | 4.52e-06 | 7.22e-06 | 6.07e-05 | 5.84e-04 | 6.28e-03 | + + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + + +[list-reverse-method]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface diff --git a/exercises/practice/reverse-string/.approaches/built-in-list-reverse/snippet.txt b/exercises/practice/reverse-string/.approaches/built-in-list-reverse/snippet.txt new file mode 100644 index 0000000000..8a999c3831 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/built-in-list-reverse/snippet.txt @@ -0,0 +1,5 @@ +def reverse(text): + output = list(text) + output.reverse() + + return ''.join(output) \ No newline at end of file diff --git a/exercises/practice/reverse-string/.approaches/built-in-reversed/content.md b/exercises/practice/reverse-string/.approaches/built-in-reversed/content.md new file mode 100644 index 0000000000..6205038262 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/built-in-reversed/content.md @@ -0,0 +1,54 @@ +# Use the built-in reversed() and Unpack with join() + + +```python +def reverse(text): + return (''.join(reversed(text))) +``` + +This approach calls the built-in `reversed()` function to return a [reverse iterator](https://docs.python.org/3/library/functions.html#reversed) that is then unpacked by `str.join()`. +This is equivalent to using a reverse slice, but incurs a bit of extra overhead due to the unpacking/iteration needed by `str.join()`. +This takes `O(n)` time and `O(n)` space for the reversed copy. + + +```python +def reverse(text): + output = '' + for index in reversed(range(len(text))): + output += text[index] + return output +``` + +This version uses `reversed()` to reverse a `range()` object rather than feed a start/stop/step to `range()` itself. +It then uses the reverse range to iterate over the input string and concatenate each code point to a new 'output' string. +This has over-complicated `reversed()`, as it can be called directly on the input string with almost no overhead. +This has also incurs the performance hit of repeated concatenation to the 'output' string. + +While this approach _looks_ as if it would be similar to the first, it is actually `O(n**2)` in time complexity due to string concatenation. +It was also the slowest in benchmarks. + + +## Timings vs Reverse Slice + + +As a (very) rough comparison, below is a timing table for these functions vs the canonical reverse slice: + +While `reversed()` is very fast, the call to `join()` to unpack slows things down compared to using a reverse slice. +For long strings, this slight overhead starts to become significant. +Using `reversed()` but concatenating to a string is non-performant in this context. + + +| **string length >>>>** | 5 | 11 | 22 | 52 | 66 | 86 | S142 | 1420 | 14200 | 142000 | +|------------------------ |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| reverse slice | 1.70e-07 | 1.78e-07 | 1.89e-07 | 2.10e-07 | 2.25e-07 | 2.40e-07 | 3.56e-07 | 1.52e-06 | 1.22e-05 | 1.20e-04 | +| reverse reversed | 3.71e-07 | 4.77e-07 | 6.78e-07 | 1.20e-06 | 1.63e-06 | 1.01e-06 | 2.78e-06 | 2.47e-05 | 2.44e-04 | 2.40e-03 | +| reverse reversed range | 6.34e-07 | 1.05e-06 | 1.85e-06 | 3.85e-06 | 4.73e-06 | 6.10e-06 | 9.77e-06 | 1.44e-04 | 1.53e-03 | 1.89e-02 | + + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface diff --git a/exercises/practice/reverse-string/.approaches/built-in-reversed/snippet.txt b/exercises/practice/reverse-string/.approaches/built-in-reversed/snippet.txt new file mode 100644 index 0000000000..a45b911005 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/built-in-reversed/snippet.txt @@ -0,0 +1,2 @@ +def reverse(text): + return (''.join(reversed(text))) diff --git a/exercises/practice/reverse-string/.approaches/config.json b/exercises/practice/reverse-string/.approaches/config.json new file mode 100644 index 0000000000..24fddb8f3e --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/config.json @@ -0,0 +1,57 @@ +{ + "introduction": { + "authors": ["BethanyG", "colinleach"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "e124fe69-dbef-4aaf-8910-706b5e3ce6bd", + "slug": "sequence-slicing", + "title": "Sequence Slicing", + "blurb": "Use a slice with a negative step to reverse the string.", + "authors": ["BethanyG"] + }, + { + "uuid": "cbe2766f-e02f-4160-8227-eead7b4ca9fb", + "slug": "iteration-and-concatenation", + "title": "Iteration and Concatenation", + "blurb": "Iterate through the codepoints and concatenate them to a new string.", + "authors": ["BethanyG"] + }, + { + "uuid": "894b1c9b-e256-471e-96f6-02453476ccc4", + "slug": "backward-iteration-with-range", + "title": "Backward iteration with Range", + "blurb": "Use a negative step with range() to iterate backward and append to a new string.", + "authors": ["BethanyG"] + }, + { + "uuid": "722e8d0e-a8d1-49a7-9b6f-38da0f7380e6", + "slug": "list-and-join", + "title": "Make a list and use join()", + "blurb": "Create a list from the string and use join to make a new string.", + "authors": ["BethanyG"] + }, + { + "uuid": "b2c8e7fa-8265-4221-b0be-c1cd13166925", + "slug": "built-in-list-reverse", + "title": "Use the built-in list.reverse() function.", + "blurb": "Create a list of codepoints, use list.reverse() to reverse in place, and join() to make a new string.", + "authors": ["BethanyG"] + }, + { + "uuid": "cbb4411a-4652-45d7-b73c-ca116ccd4f02", + "slug": "built-in-reversed", + "title": "Use the built-in reversed() function.", + "blurb": "Use reversed() and unpack it with join() to make a new string.", + "authors": ["BethanyG"] + }, + { + "uuid": "1267e48f-edda-44a7-a441-a36155a8fba2", + "slug": "additional-approaches", + "title": "Additional approaches that are further afield", + "blurb": "Additional interesting approaches.", + "authors": ["BethanyG"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/reverse-string/.approaches/introduction.md b/exercises/practice/reverse-string/.approaches/introduction.md new file mode 100644 index 0000000000..180d4716a2 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/introduction.md @@ -0,0 +1,171 @@ +# Introduction + + +The goal of the Reverse String exercise is to output a given string in reverse order. +It can be solved in a lot of different ways in Python, with a near-endless amount of variation. + +However, not all strategies are efficient, concise, or small in memory. +Care must be taken to not inadvertently slow down the code by using methods that don't scale well. + +Additionally, most 'canonical' solutions for reversing a string using the Python standard library do not account for Unicode text beyond the ASCII (0-127) range. + + +In this introduction, we cover six general approaches and an additional group of 'interesting' takes, but there are many more techniques that could be used. + +1. Sequence Slice with Negative Step +2. Iteration with String Concatenation +3. Reverse Iteration with Range() +4. Make a list and Use str.join() +5. Make a list and use list.reverse() +6. Use the built-in reversed() +7. Other [interesting approaches][approach-additional-approaches] + +We encourage you to experiment and get creative with the techniques you use, and see how it changes the way you think about the problem and think about Python. + + +And while Unicode text is outside the core tests for this exercise (_there are optional tests in the test file you can enable for Unicode_), we encourage you to give reversing strings that have non ASCII text a try. + + +## Approach: Sequence Slice with a Negative Step + +```python +def reverse(text): + return text[::-1] +``` + +This is "THE" canonical solution, _provided_ you know what encoding and character sets you are dealing with. +For example, if you know all of your text is **always** going to be within the ASCII space, this is by far the most succinct and performant way to reverse a string in Python. + +For more details, see the [sequence slicing approach][approach-sequence-slicing] + + +## Approach: Iterate over the String; Concatenate to a New String + + +```python +def reverse(text): + output = '' + for codepoint in text: + output = codepoint + output + return output +``` + +This approach iterates over the string, concatenating each codepoint to a new string. +This approach and its variants avoid all use of built-ins such as `range()`, `reversed()`, and `list.reverse()`. +But for very long strings, this approach can degrade performance toward O(n**2). + +For more information and relative performance timings for this group, check out the [iteration and concatenation][approach-iteration-and-concatenation] approach. + + +## Approach: Use range() to Iterate Backwards over the String, Append to New String + + +```python +def reverse(text): + new_word = "" + + for index in range(len(text) - 1, -1, -1): #For 'Robot', this is 4 (start) 0 (stop), iterating (4,3,2,1,0) + new_word += text[index] + return new_word +``` + +This method uses the built-in [`range()`][range] object to iterate over text right-to-left, adding each codepoint to the 'new_word' string. +This is essentially the same technique as the approach above, but incurs slightly less overhead by avoiding the potential performance hit of _prepending_ to the 'new_word' string, or creating index or tracking variables. + +For very long strings, this approach will still degrade to `O(n**2)` performance, due to the use of string concatenation. +Using `''.join()` here can avoid the concatenation penalty. +For more information and relative performance timings for this group, check out the [backwards iteration with range][approach-backward-iteration-with-range] approach. + + +## Approach: Create a List and Use str.join() to make new String. + + +```python +def reverse(text): + output = [] + + for codepoint in text: + output.insert(0,codepoint) + return "".join(output) +``` + +This approach either breaks the string up into a list of codepoints to swap or creates an empty list as a "parking place" to insert or append codepoints. +It then iterates over the text, swapping, inserting, or appending each codepoint to the output list. +Finally, `str.join()` is used to re-assemble the `list` into a string. + +For more variations and relative performance timings for this group, check out the [list and join][approach-list-and-join] approach. + + +## Approach: Make the Input Text a List & Use list.reverse() to Reverse in Place + + +```python +def reverse(text): + output = list(text) + output.reverse() + + return ''.join(output) +``` + +This approach turns the string into a list of codepoints and then uses the `list.reverse()` method to re-arrange the list _in place_. +After the reversal of the list, `str.join()` is used to create the reversed string. + +For more details, see the [built in list.reverse()][approach-built-in-list-reverse] approach. + + +## Approach: Use the built-in reversed() Function & join() to Unpack + + +```python +def reverse(text): + return (''.join(reversed(text))) +``` + +This approach calls the built-in `reversed()` function to return a [reverse iterator](https://docs.python.org/3/library/functions.html#reversed) that is then unpacked by `str.join()`. +This is equivalent to using a reverse slice, but incurs a bit of extra overhead due to the unpacking/iteration needed by `str.join()`. + +For more details, see the [built-in reversed()][approach-built-in-reversed] approach. + + +```python +def reverse(text): + output = '' + for index in reversed(range(len(text))): + output += text[index] + return output +``` + +This version uses `reversed()` to reverse a `range()` object rather than feed a start/stop/step to `range()` itself. +It then uses the reverse range to iterate over the input string and concatenate each code point to a new 'output' string. +This has over-complicated `reversed()` a bit, as it can be called directly on the input string with almost no overhead. +This has also incurred the performance hit of repeated concatenation to the 'output' string. + +## Other Interesting Approaches + +These range from using recursion to converting text to bytes before processing. +Some even use `map()` and or a `lambda` + +Take a look at the [additional approaches][approach-additional-approaches] 'approach' for more details and timings. + + +## Which Approach to Use? + +The fastest and most canonical by far is the reverse slice. +Unless you are in an interview situation where you need to "show your work", or working with varied Unicode outside the ASCII range, a reverse slice is the easiest and most direct method of reversal. + +A reverse slice will also work well for varied Unicode that has been pre-processed to ensure that multibyte characters and combined letters with diacritical and accent marks ('extended graphemes') remain grouped. + + +For other scenarios, converting the intput text to a `list`, swapping or iterating, and then using `join()` is recommended. + +To compare performance of these approach groups, see the [Performance article][article-performance]. + +[approach-additional-approaches]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/additional-approaches +[approach-backward-iteration-with-range]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/backward-iteration-with-range +[approach-built-in-list-reverse]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/built-in-list-reverse +[approach-built-in-reversed]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/built-in-reversed +[approach-iteration-and-concatenation]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/iteration-and-concatenation +[approach-list-and-join]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/list-and-join +[approach-sequence-slicing]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/sequence-slicing +[article-performance]: https://exercism.org/tracks/python/exercises/reverse-string/articles/performance +[range]: https://docs.python.org/3/library/stdtypes.html#range diff --git a/exercises/practice/reverse-string/.approaches/iteration-and-concatenation/content.md b/exercises/practice/reverse-string/.approaches/iteration-and-concatenation/content.md new file mode 100644 index 0000000000..7acc4d7c66 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/iteration-and-concatenation/content.md @@ -0,0 +1,110 @@ +# Iteration and Concatenation + + +```python +def reverse(text): + output = '' + for codepoint in text: + output = codepoint + output + return output +``` + +The variations here all iterate over the string, concatenating each codepoint to a new string. +While this avoids all use of built-ins such as `range()`, `reversed()`, and `list.reverse()`, it incurs both a memory and speed penalty over using a reverse slice. + +Strings are immutable in Python. +Using concatenation via `+` or `+=` forces the re-creation of the 'output' string for _every codepoint added from the input string._ +That means the code has a minimum time complexity of `O(m + n)`, where `n` is the length of the text being iterated over, and `m` is the number of concatenations to the 'output' string. +For some more detail on `O(n + m)` vs `O(n)`, see this [Stack Overflow post][time-complexity-omn-vs-on]. +The code also uses `O(n)` space to store 'output'. + +As input strings grow longer, concatenation can become even more problematic, and performance can degrade to `O(n**2)`, as longer and longer shifts and reallocations occur in memory. +In fact, the "standard" way to describe the time complexity of this code is to say that is O(n**2), or quadratic. + +Interestingly, CPython includes an optimization that attempts to avoid the worst of the shift and reallocation behavior by reusing memory when it detects that a string append is happening. +Because the code above _prepends_ the codepoint to the left-hand side of 'output', this optimization cannot be used. +Even in cases where strings are appended to, this optimization cannot be relied upon to be stable and is not transferable to other implementations of Python. + +For some interesting reading on this topic, see these Stack Overflow posts: +- [Time Complexity of String Concatenation in Python][time-complexity-of-string-concatenation-in-python], +- [Time Complexity of Iterative String Append][time-complexity-of-iterative-string-append], +- [Most efficient String Concatenation Method in Python][most-efficient-string-concatenation-method-in-Python], +- [join() is faster than +, but what is wrong here?][join() is faster than +, but what is wrong here?], and +- [Is += bad practice in Python?][is += bad practice in Python?] + +To see the difference between reverse slicing and looping in terms of steps, check out [slicing verses iterating+concatenation][python-tutor] at the PythonTutor site. + + +## Variation #1: Using a While Loop and a Negative Index + + +```python +def reverse(text): + output = '' + index = -1 + + while index >= -len(text): + output += text[index] + index -= 1 + return output +``` + +This solution uses a while loop to "count down" the length of the string using a negative index. +Each number is used to index into the input string and concatenate the resulting codepoint to a new string. +Because each index is further from zero than the last, this has the effect of "iterating backward" over the input string. + +This approach incurs additional overhead for length checking the input string repeatedly in the loop, and setting/decrementing the index variable, both of which can be avoided by using the built-in `range()` object. +Overall, this was the slowest of the three variations when timed. + + +## Variation #2: Using a While Loop with a Positive Index + + +```python +def reverse(text): + result ='' + index = len(text)-1 + + while index >= 0: + result += text[index] + index -= 1 + return result +``` + +This solution uses a while loop to "count down" the length of the string until it reaches zero using a positive index. +Each number is used to index into the input string and concatenate the resulting codepoint to a new string. +Because each index is closer to zero than the last, this has the effect of also "iterating backward" over the input string. +Algorithmically, this takes as much tine and space as the code samples above, since it uses an intermediate string for the reversal and must loop through every codepoint in the input. + + +## Timings vs Reverse Slice + + +As seen in the table below, all of these approaches are slower than using a reverse slice. +Interestingly, iteration + prepending to the string is fastest in this group for strings under length 1420. +But keep in mind that in general, string concatenation and prepending should be avoided for any 'industrial strength' use cases. + + +| **string length >>>>** | 5 | 11 | 22 | 52 | 66 | 86 | 142 | 1420 | 14200 | 142000 | +|------------------------ |---------- |---------- |---------- |---------- |---------- |---------- |---------- |---------- |---------- |---------- | +| reverse slice | 1.66e-07 | 1.73e-07 | 1.88e-07 | 1.12e-07 | 2.15e-07 | 2.32e-07 | 3.46e-07 | 1.42e-06 | 1.18e-05 | 1.15e-04 | +| reverse string prepend | 4.28e-07 | 8.05e-07 | 1.52e-06 | 3.45e-06 | 4.82e-06 | 5.55e-06 | 9.83e-06 | 2.23e-04 | 2.96e-03 | 5.17e-01 | +| reverse positive index | 4.65e-07 | 8.85e-07 | 1.73e-06 | 3.70e-06 | 4.83e-06 | 6.55e-06 | 1.01e-05 | 1.54e-04 | 1.60e-03 | 2.61e-02 | +| reverse negative index | 5.65e-07 | 1.32e-06 | 2.61e-06 | 5.91e-06 | 7.62e-06 | 4.00e-06 | 1.62e-05 | 2.16e-04 | 2.19e-03 | 2.48e-02 | + + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + +[python-tutor]: https://pythontutor.com/render.html#code=def%20reverse_loop%28text%29%3A%0A%20%20%20%20output%20%3D%20''%0A%20%20%20%20for%20letter%20in%20text%3A%0A%20%20%20%20%20%20%20%20output%20%3D%20letter%20%2B%20output%0A%20%20%20%20return%20output%0A%20%20%20%20%0Adef%20reverse_slice%28text%29%3A%0A%20%20%20%20return%20text%5B%3A%3A-1%5D%0A%20%20%20%20%0A%0Aprint%28reverse_loop%28'Robot'%29%29%0Aprint%28reverse_slice%28'Robot'%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +[is += bad practice in Python?]: https://stackoverflow.com/questions/39675898/is-python-string-concatenation-bad-practice +[join() is faster than +, but what is wrong here?]: https://stackoverflow.com/a/1350289 +[most-efficient-string-concatenation-method-in-Python]: https://stackoverflow.com/questions/1316887/what-is-the-most-efficient-string-concatenation-method-in-python +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[time-complexity-of-iterative-string-append]: https://stackoverflow.com/questions/34008010/is-the-time-complexity-of-iterative-string-append-actually-on2-or-on +[time-complexity-of-string-concatenation-in-python]: https://stackoverflow.com/questions/37133547/time-complexity-of-string-concatenation-in-python +[time-complexity-omn-vs-on]: https://cs.stackexchange.com/questions/139307/time-complexity-omn-vs-on +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface diff --git a/exercises/practice/reverse-string/.approaches/iteration-and-concatenation/snippet.txt b/exercises/practice/reverse-string/.approaches/iteration-and-concatenation/snippet.txt new file mode 100644 index 0000000000..d4758b0601 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/iteration-and-concatenation/snippet.txt @@ -0,0 +1,5 @@ +def reverse(text): + output = '' + for codepoint in text: + output = codepoint + output + return output diff --git a/exercises/practice/reverse-string/.approaches/list-and-join/content.md b/exercises/practice/reverse-string/.approaches/list-and-join/content.md new file mode 100644 index 0000000000..07f7daa03f --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/list-and-join/content.md @@ -0,0 +1,148 @@ +# Create a List and Use str.join() to Make A New String + + +To avoid performance issues with concatenating to a string, this group of approaches uses one or more `list`s to perform swaps or reversals before joining the codepoints back into a string. +This avoids the `O(n**2)` danger of repeated shifting/reallocation when concatenating long strings. +However, the use of `join()` and other techniques still make all of these solutions `O(n)` - `O(n+m)` in time complexity. + + +```python +def reverse(text): + output = [] + + for codepoint in text: + output.insert(0,codepoint) + return "".join(output) +``` + +The code above iterates over the codepoints in the input text and uses `list.insert()` to insert each one into the output list. +Note that `list.insert(0, codepoint)` _prepends_, which is very inefficient for `lists`, while appending takes place in (amortized) O(1) time. +So this code incurs a time penalty because it forces repeated shifts of every element in the list with every insertion. +A small re-write using `range()` to change the iteration direction will boost performance: + + +## Variation #1: Use Range to Iterate Over the String Backward and list.append() to Output + + +```python +def reverse(text): + output = [] + length = len(text)-1 + + for index in range(length, -1, -1): + output.append(text[index]) + return "".join(output) +``` + +This code iterates backward over the string using `range()`, and can therefore use `list.append()` to append to the output list in (amortized) constant time. +However, the use of `join()` to unpack the list and create a string still makes this `O(n)`. +This also takes `O(n)` space for the output `list`. + + +## Variation #2: Convert Text to List and Use range() to Iterate over 1/2 the String, Swapping Values + + +```python +def reverse(text): + output = list(text) + length = len(text) // 2 #Cut the amount of iteration in half + + for index in range(length): + + #Swap values at given indexes + output[index], output[length - index - 1] = output[length - index - 1], output[index] + return ''.join(output) +``` + + +This variation calculates a median which is then used with `range()` in a `for loop` to iterate over _half_ the indexes in the 'output' list, swapping values into their reversed places. +`str.join()` is then used to create a new string. +This technique is quite speedy, and re-arranges the list of codepoints 'in place', avoiding expensive string concatenation. +It is still `O(n)` time complexity because `list()` and `join()` each force iteration over the entire length of the input string. + + +## Variation #3: Convert Text to List, Use Start and End Variables to Iterate and Swap Values + + +```python +def reverse(text): + output = list(text) + start = 0 + end = len(text) - 1 + + while start < end: + #Swap values in output until the indexes meet at the 'center' + output[start], output[end] = output[end], output[start] + start += 1 + end -= 1 + return "".join(output) +``` + + +This variation 'automatically' finds the midpoint by incrementing and decrementing 'start' and 'end' variables. +Otherwise, it is identical to variation 2. + + +## Variation #4: Convert Text to Bytearray, Iterate and Swap + + +```python +def reverse(text): + output = bytearray(text.encode("utf-8")) + length = len(output) + + for index in range(length//2): + output[index], output[length-1-index] = output[length-1-index], output[index] + return output.decode("utf-8") +``` + + +This variation is operationally the same as variations #2 & #3 above, except that it encodes the string to a `utf-8` [bytearray](https://docs.python.org/3/library/stdtypes.html#bytearray). + It then iterates over the bytearray to perform the swaps. +Finally, the bytearray is decoded into a `utf-8` string to return the reversed word. +This incurs overhead when encoding/decoding to and from the `bytearray`. +This also throws an ` UnicodeDecodeError: invalid start byte` when working with any multi-byte codepoints because no check was conducted to keep multibyte codepoints grouped together during the reversal. + +Because of this issue, no timings are available for this variation. +For code that keeps bytes together correctly, see the bytearray variation in the [additional approaches][approach-additional-approaches] approach. + + +## Variation #5: Use Generator Expression with Join to Iterate Backwards Over Codepoints List + +```python +def reverse(text): + codepoints = list(text) + length = len(text) - 1 + return "".join(codepoints[index] for index in range(length, -1, -1)) +``` + +This variation puts the for/while loop used in other strategies directly into `join()` using a generator expression. +The text is first converted to a list and the generator-expression "swaps" the codepoints over the whole `list`, using `range()` for the indexes. +Interestingly, because of the work to create and manage the generator, this variation is actually _slower_ than using an auxiliary `list` and `loop` to manage codepoints and then calling `join()` separately. + + +## Timings vs Reverse Slice + + +As a (very) rough comparison, below is a timing table for these functions vs the canonical reverse slice: + + +| **string lengths >>>>** | 5 | 11 | 22 | 52 | 66 | 86 | 142 | 1420 | 14200 | 142000 | +|------------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| reverse slice | 1.67e-07 | 1.76e-07 | 1.85e-07 | 2.03e-07 | 2.12e-07 | 2.32e-07 | 3.52e-07 | 1.47e-06 | 1.20e-05 | 1.17e-04 | +| reverse auto half swap | 4.59e-07 | 7.53e-07 | 1.16e-06 | 2.25e-06 | 3.08e-06 | 3.80e-06 | 5.97e-06 | 7.08e-05 | 7.21e-04 | 7.18e-03 | +| reverse half swap | 6.34e-07 | 9.24e-07 | 1.51e-06 | 2.91e-06 | 3.71e-06 | 4.53e-06 | 7.52e-06 | 2.52e-04 | 1.01e-03 | 1.05e-02 | +| reverse append | 6.44e-07 | 1.00e-06 | 1.56e-06 | 3.28e-06 | 4.48e-06 | 5.54e-06 | 8.89e-06 | 2.20e-04 | 8.73e-04 | 9.10e-03 | +| reverse generator join | 1.02e-06 | 1.39e-06 | 2.16e-06 | 4.13e-06 | 5.31e-06 | 6.79e-06 | 1.11e-05 | 1.07e-04 | 1.07e-03 | 1.05e-02 | +| reverse insert | 5.29e-07 | 9.10e-07 | 1.64e-06 | 3.77e-06 | 4.90e-06 | 6.86e-06 | 1.14e-05 | 2.70e-04 | 2.35e-02 | 2.74e+00 | + + + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface +[approach-additional-approaches]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/additional-approaches diff --git a/exercises/practice/reverse-string/.approaches/list-and-join/snippet.txt b/exercises/practice/reverse-string/.approaches/list-and-join/snippet.txt new file mode 100644 index 0000000000..4ecd5cb181 --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/list-and-join/snippet.txt @@ -0,0 +1,8 @@ +def reverse(text): + output = list(text) + start, end = 0, len(text) - 1 + while start < end: + output[start], output[end] = output[end], output[start] + start += 1 + end -= 1 + return "".join(output) \ No newline at end of file diff --git a/exercises/practice/reverse-string/.approaches/sequence-slicing/content.md b/exercises/practice/reverse-string/.approaches/sequence-slicing/content.md new file mode 100644 index 0000000000..2c85dbf19c --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/sequence-slicing/content.md @@ -0,0 +1,48 @@ +# Sequence Slice with Negative Step + + +```python +def reverse(text): + return text[::-1] +``` + +This approach uses Python's negative indexes and _[sequence slices][sequence slicing]_ to iterate over the string in reverse order, returning a reversed copy. + + + + + + +
index from left ⟹






+ +| 0
👇🏾 | 1
👇🏾 | 2
👇🏾 | 3
👇🏾 | 4
👇🏾 | 5
👇🏾 | +|:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| P | y | t | h | o | n | +| 👆🏾
-6 | 👆🏾
-5 | 👆🏾
-4 | 👆🏾
-3 | 👆🏾
-2 | 👆🏾
-1 | +





⟸ index from right
+ +Slices use **`[ : : ]`** syntax. +The space before the first `:` indicates which index to start iterating from (_inclusive_), the space before the second `:` indicates which index to stop before (_exclusive_), and the final space after the second `:` indicates the direction of iteration and size of the 'step'. + A positive step moves left --> right and a negative step moves right --> left. + If start/stop indexes are omitted, Python assumes 'start of string' and 'end of string'. +Omitting the step defaults to a step of +1, but any size step can be used. +Slices return a _copy_ of the original object. +This same syntax works on `strings`, `bytearray`, `lists`, `tuples`, and `ranges`, which are all sequence types. + + +Reverse slicing has `O(n)` time complexity - the amount of time/work scales directly with the length of the string being iterated through and reversed. +And since slicing returns copy, the space for the copy also scales with the size of the input. + +Using a slice on a string is roughly equivalent to looping over the string from the right-hand side, appending each codepoint to a new string. +However, the code below takes `O(n + n)` best case and `O(n**2)` worst case due to the operations needed for string concatenation. + + +```python +def reverse(text): + output = '' + for index in range(-1, -(len(text)+1), -1): + output += text[index] + return output +``` + +[sequence slicing]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations diff --git a/exercises/practice/reverse-string/.approaches/sequence-slicing/snippet.txt b/exercises/practice/reverse-string/.approaches/sequence-slicing/snippet.txt new file mode 100644 index 0000000000..86e703117a --- /dev/null +++ b/exercises/practice/reverse-string/.approaches/sequence-slicing/snippet.txt @@ -0,0 +1,2 @@ +def reverse(text): + return text[::-1] \ No newline at end of file diff --git a/exercises/practice/reverse-string/.articles/config.json b/exercises/practice/reverse-string/.articles/config.json new file mode 100644 index 0000000000..3cce3e70c5 --- /dev/null +++ b/exercises/practice/reverse-string/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "1d5866e9-6c74-411b-ab67-e986d154876e", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach for reversing a string.", + "authors": ["BethanyG"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/reverse-string/.articles/performance/code/Benchmark.py b/exercises/practice/reverse-string/.articles/performance/code/Benchmark.py new file mode 100644 index 0000000000..52c14a787c --- /dev/null +++ b/exercises/practice/reverse-string/.articles/performance/code/Benchmark.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +"""Script for timing Reverse String Solutions. + +Creates timing table and timing graphs for +multiple approaches to reversing a stirng in Python. + +Created Jan 2024 +@author: bethanygarcia +""" + + +import timeit + +import pandas as pd +import numpy as np + + +# ------------ FUNCTIONS TO TIME ------------- # + + +def reverse_slice(text): + return text[::-1] + + +def reverse_iterate_and_prepend(text): + output = '' + for codepoint in text: + output = codepoint + output + return output + + +def reverse_range(text): + return "".join(text[index] for index in range(len(text) - 1, -1, -1)) + + +def reverse_half_swap(text): + output = list(text) + length = len(text) // 2 # Cut the amount of iteration in half. + + for index in range(length): + + # Swap values at given indexes in output list. + output[index], output[length - index - 1] = output[length - index - 1], output[index] + return ''.join(output) + + +def reverse_list_reverse(text): + output = list(text) + output.reverse() + + return ''.join(output) + + +def reverse_reversed(text): + return (''.join(reversed(text))) + + +def reverse_map(text): + return "".join(map(lambda x: text[(-x - 1)], range(len(text)))) + +## ---------END FUNCTIONS TO BE TIMED-------------------- ## + + + +## -------- Timing Code Starts Here ---------------------## +# Input Data Setup for ASCII Solutions + +long = 'Sünnipäevanädalalõpupeopärastlõunaväsimatus Pneumonoultramicroscopicsilicovolcanoconiosis Aequeosalinocalcalinoceraceoaluminosocupreovitriolic' + +words = [ + 'Ramen', + 'Euouae', + 'racecar', + 'Strengths', + "I'm hungry!", + 'Otorhinolaryngological', + 'Antidisestablishmentarianism', + 'Pseudopseudohypoparathyroidism', + 'Hippopotomonstrosesquippedaliophobia', + 'Sünnipäevanädalalõpupeopärastlõunaväsimatus', + 'Aequeosalinocalcalinoceraceoaluminosocupreovitriolic', + 'Lentokonesuihkuturbiinimoottoriapumekaanikkoaliupseerioppilas', + 'Miinibaashkiminasiganibiitoosijiganibadagwiingweshiganibakwezhigan', + 'Rindfleisch­etikettierungs­überwachungs­aufgaben­übertragungs­gesetz', + 'Incomprehensibilities Otorhinolaryngological cyfrwngddarostyngedigaeth', + 'Antidisestablishmentarianism Spectrophotofluorometrically Antidisestablishmentarianism', + 'Sünnipäevanädalalõpupeopärastlõunaväsimatus Pneumonoultramicroscopicsilicovolcanoconiosis Aequeosalinocalcalinoceraceoaluminosocupreovitriolic', + long * 10, + long * 100, + long * 1000 +] + +# #Set up columns and rows for Pandas Data Frame +col_headers = [f'Str Len: {len(string)}' for string in words] +row_headers = ['reverse slice', 'iterate & prepend', 'iterate with range', 'list swap', 'list reverse', + 'reversed builtin', 'map and join'] +labels = row_headers + +# # empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# #Function List to Call When Timing +functions = [reverse_slice, reverse_iterate_and_prepend, reverse_range, reverse_half_swap, reverse_list_reverse, + reverse_reversed, reverse_map] + +# Run timings using timeit.autorange(). Run Each Set 3 Times. +for function, title in zip(functions, row_headers): + timings = [[ + timeit.Timer(lambda: function(data), globals=globals()).autorange()[1] / + timeit.Timer(lambda: function(data), globals=globals()).autorange()[0] + for data in words] for rounds in range(3)] + + # Only the fastest Cycle counts. + timing_result = min(timings) + + # timing_result = [round(min(timeit.repeat(lambda: function(data), repeat=3, number=1000000, globals=globals())), 6) for data in words_II] + print(f'{title}', f'Timings : {timing_result}') + + # Insert results into the dataframe + df.loc[title, 'Str Len: 5':'Str Len: 142000'] = timing_result + +# The next bit is useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".2e")) diff --git a/exercises/practice/reverse-string/.articles/performance/content.md b/exercises/practice/reverse-string/.articles/performance/content.md new file mode 100644 index 0000000000..dee0b06d74 --- /dev/null +++ b/exercises/practice/reverse-string/.articles/performance/content.md @@ -0,0 +1,60 @@ +# Performance + +In this article, we'll find out how to most efficiently reverse a string in Python. + +The approaches [introduction][introduction] lists six groups of approaches: + +1. [Sequence Slice with Negative Step][approach-sequence-slicing] +2. [Iteration with String Concatenation][approach-iteration-and-concatenation] +3. [Reverse Iteration with Range()][approach-backward-iteration-with-range] +4. [Make a list and Use str.join()][approach-list-and-join] +5. [Make a list and use list.reverse()][approach-built-in-list-reverse] +6. [Use the built-in reversed()][approach-built-in-reversed] +7. Other [interesting approaches][approach-additional-approaches] + +For our performance investigations, we will compare the most performant from each group and a seventh approach using [`map()`][map in alternative approaches]. + +## Benchmarks + +To benchmark these functions, we wrote a small [benchmarking script][benchmark script] using the [timeit][timeit] module along with third-party libraries [numpy][numpy] and [pandas][pandas]. + + +The reverse slice is by far the most performant, followed by the built-ins `list.reverse()` and `reversed()`. +Iteration and concatenation is next, due to the CPython string optimization (_see the [iteration and concatenation][approach-iteration-and-concatenation] approach for all the details_), but this approach slows radically for strings longer than 142 characters. + + +With more than 142 characters, using a list, swapping positions, and joining via `join()` is the most performant method that doesn't use built-ins. +Using `map()` with `join()` was the least performant approach overall. + + + +| **string length >>>>** | 5 | 11 | 22 | 52 | 66 | 86 | 142 | 1420 | 14200 | 142000 | +|-------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| reverse slice | 1.71e-07 | 1.73e-07 | 1.86e-07 | 2.07e-07 | 2.19e-07 | 2.36e-07 | 3.49e-07 | 1.51e-06 | 1.19e-05 | 1.18e-04 | +| list reverse | 3.29e-07 | 4.28e-07 | 5.73e-07 | 8.92e-07 | 1.20e-06 | 1.51e-06 | 2.34e-06 | 1.94e-05 | 1.90e-04 | 1.91e-03 | +| reversed builtin | 3.68e-07 | 4.83e-07 | 6.98e-07 | 1.20e-06 | 1.62e-06 | 2.03e-06 | 2.71e-06 | 2.42e-05 | 2.35e-04 | 2.36e-03 | +| iterate & concatenate | 4.18e-07 | 8.10e-07 | 1.49e-06 | 3.49e-06 | 4.35e-06 | 6.18e-06 | 4.12e-06 | 2.03e-04 | 3.31e-03 | 4.61e-01 | +| list swap | 6.43e-07 | 4.00e-07 | 1.54e-06 | 3.01e-06 | 2.06e-06 | 4.71e-06 | 7.47e-06 | 8.97e-05 | 2.52e-03 | 1.02e-02 | +| iterate with range | 9.19e-07 | 1.35e-06 | 2.12e-06 | 4.15e-06 | 5.23e-06 | 6.60e-06 | 1.10e-05 | 1.05e-04 | 1.02e-03 | 1.07e-02 | +| map and join | 9.56e-07 | 1.72e-06 | 3.08e-06 | 6.27e-06 | 7.96e-06 | 1.03e-05 | 1.71e-05 | 1.70e-04 | 1.68e-03 | 1.70e-02 | + + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + +[approach-additional-approaches]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/additional-approaches +[approach-backward-iteration-with-range]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/backward-iteration-with-range +[approach-built-in-list-reverse]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/built-in-list-reverse +[approach-built-in-reversed]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/built-in-reversed +[approach-iteration-and-concatenation]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/iteration-and-concatenation +[approach-list-and-join]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/list-and-join +[approach-sequence-slicing]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/sequence-slicing +[introduction]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/introduction.md +[map in alternative approaches]: .org/tracks/python/exercises/reverse-string/.approaches/additional-approaches#Using-`map()`-and-`lambbda`-with-`Join()`-Instead-of-a-Loop +[numpy]: https://numpy.org/ +[pandas]: https://pandas.pydata.org/ +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface +[benchmark script]: https://exercism.org/tracks/python/exercises/reverse-string/.articles/code/Benchmark.py \ No newline at end of file diff --git a/exercises/practice/reverse-string/.articles/performance/snippet.md b/exercises/practice/reverse-string/.articles/performance/snippet.md new file mode 100644 index 0000000000..3864547209 --- /dev/null +++ b/exercises/practice/reverse-string/.articles/performance/snippet.md @@ -0,0 +1,8 @@ +| | 5 | 142000 | +| reverse slice | 1.71e-07 | 1.18e-04 | +| list reverse | 3.29e-07 | 1.91e-03 | +| reversed builtin | 3.68e-07 | 2.36e-03 | +| iterate & prepend | 4.18e-07 | 4.61e-01 | +| list swap | 6.43e-07 | 1.02e-02 | +| iterate with range | 9.19e-07 | 1.07e-02 | +| map and join | 9.56e-07 | 1.70e-02 | \ No newline at end of file From 97d715e5472ce2ca2459c85e2a5a5f7977bc800e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 15 Feb 2025 13:40:49 -0800 Subject: [PATCH 774/826] Update to configs to ensure author credit. (#3869) --- .../reverse-string/.approaches/config.json | 16 ++++++++-------- .../reverse-string/.articles/config.json | 2 +- .../.articles/performance/code/Benchmark.py | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/exercises/practice/reverse-string/.approaches/config.json b/exercises/practice/reverse-string/.approaches/config.json index 24fddb8f3e..6623bb52d9 100644 --- a/exercises/practice/reverse-string/.approaches/config.json +++ b/exercises/practice/reverse-string/.approaches/config.json @@ -1,6 +1,6 @@ { "introduction": { - "authors": ["BethanyG", "colinleach"], + "authors": ["bethanyg", "colinleach"], "contributors": [] }, "approaches": [ @@ -9,49 +9,49 @@ "slug": "sequence-slicing", "title": "Sequence Slicing", "blurb": "Use a slice with a negative step to reverse the string.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "cbe2766f-e02f-4160-8227-eead7b4ca9fb", "slug": "iteration-and-concatenation", "title": "Iteration and Concatenation", "blurb": "Iterate through the codepoints and concatenate them to a new string.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "894b1c9b-e256-471e-96f6-02453476ccc4", "slug": "backward-iteration-with-range", "title": "Backward iteration with Range", "blurb": "Use a negative step with range() to iterate backward and append to a new string.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "722e8d0e-a8d1-49a7-9b6f-38da0f7380e6", "slug": "list-and-join", "title": "Make a list and use join()", "blurb": "Create a list from the string and use join to make a new string.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "b2c8e7fa-8265-4221-b0be-c1cd13166925", "slug": "built-in-list-reverse", "title": "Use the built-in list.reverse() function.", "blurb": "Create a list of codepoints, use list.reverse() to reverse in place, and join() to make a new string.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "cbb4411a-4652-45d7-b73c-ca116ccd4f02", "slug": "built-in-reversed", "title": "Use the built-in reversed() function.", "blurb": "Use reversed() and unpack it with join() to make a new string.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "1267e48f-edda-44a7-a441-a36155a8fba2", "slug": "additional-approaches", "title": "Additional approaches that are further afield", "blurb": "Additional interesting approaches.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] } ] } \ No newline at end of file diff --git a/exercises/practice/reverse-string/.articles/config.json b/exercises/practice/reverse-string/.articles/config.json index 3cce3e70c5..e9b0971751 100644 --- a/exercises/practice/reverse-string/.articles/config.json +++ b/exercises/practice/reverse-string/.articles/config.json @@ -5,7 +5,7 @@ "slug": "performance", "title": "Performance deep dive", "blurb": "Deep dive to find out the most performant approach for reversing a string.", - "authors": ["BethanyG"] + "authors": ["bethanyg", "colinleach"] } ] } \ No newline at end of file diff --git a/exercises/practice/reverse-string/.articles/performance/code/Benchmark.py b/exercises/practice/reverse-string/.articles/performance/code/Benchmark.py index 52c14a787c..7846a0e9fc 100644 --- a/exercises/practice/reverse-string/.articles/performance/code/Benchmark.py +++ b/exercises/practice/reverse-string/.articles/performance/code/Benchmark.py @@ -4,7 +4,8 @@ """Script for timing Reverse String Solutions. Creates timing table and timing graphs for -multiple approaches to reversing a stirng in Python. +multiple approaches to reversing a string in Python. +Adapted from code written by colinleach. Created Jan 2024 @author: bethanygarcia From 2a69f421d7a8a08708c85e757503e78fbe9b529a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 15 Feb 2025 16:17:26 -0800 Subject: [PATCH 775/826] fixed broken links in summary doc. (#3870) --- .../reverse-string/.approaches/introduction.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/exercises/practice/reverse-string/.approaches/introduction.md b/exercises/practice/reverse-string/.approaches/introduction.md index 180d4716a2..b20a312fdb 100644 --- a/exercises/practice/reverse-string/.approaches/introduction.md +++ b/exercises/practice/reverse-string/.approaches/introduction.md @@ -160,12 +160,12 @@ For other scenarios, converting the intput text to a `list`, swapping or iterati To compare performance of these approach groups, see the [Performance article][article-performance]. -[approach-additional-approaches]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/additional-approaches -[approach-backward-iteration-with-range]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/backward-iteration-with-range -[approach-built-in-list-reverse]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/built-in-list-reverse -[approach-built-in-reversed]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/built-in-reversed -[approach-iteration-and-concatenation]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/iteration-and-concatenation -[approach-list-and-join]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/list-and-join -[approach-sequence-slicing]: https://exercism.org/tracks/python/exercises/reverse-string/.approaches/sequence-slicing +[approach-additional-approaches]: https://exercism.org/tracks/python/exercises/reverse-string/approaches/additional-approaches +[approach-backward-iteration-with-range]: https://exercism.org/tracks/python/exercises/reverse-string/approaches/backward-iteration-with-range +[approach-built-in-list-reverse]: https://exercism.org/tracks/python/exercises/reverse-string/approaches/built-in-list-reverse +[approach-built-in-reversed]: https://exercism.org/tracks/python/exercises/reverse-string/approaches/built-in-reversed +[approach-iteration-and-concatenation]: https://exercism.org/tracks/python/exercises/reverse-string/approaches/iteration-and-concatenation +[approach-list-and-join]: https://exercism.org/tracks/python/exercises/reverse-string/approaches/list-and-join +[approach-sequence-slicing]: https://exercism.org/tracks/python/exercises/reverse-string/approaches/sequence-slicing [article-performance]: https://exercism.org/tracks/python/exercises/reverse-string/articles/performance [range]: https://docs.python.org/3/library/stdtypes.html#range From 58d82a041f65e3270a49c4ef46a441261b87eb97 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 20 Feb 2025 16:05:56 -0800 Subject: [PATCH 776/826] [Bank Account]: Fix instructions.append.md Admonition (#3872) Misspelling of Exercism as well as backticks broke formatting on site. --- .../practice/bank-account/.docs/instructions.append.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/bank-account/.docs/instructions.append.md b/exercises/practice/bank-account/.docs/instructions.append.md index f49101c319..0f71c081eb 100644 --- a/exercises/practice/bank-account/.docs/instructions.append.md +++ b/exercises/practice/bank-account/.docs/instructions.append.md @@ -1,6 +1,6 @@ # Instructions append -````exercim/note +~~~~exercism/note Python doesn't support "true" concurrency due to the [Global Interpreter Lock][GIL]. While work is ongoing to create support for [free-threading in Python][free-threading], it is still experimental. Current standard library solutions such as [multiprocessing][multiprocessing-module] and [threading][threading-module] are difficult to implement with the current track tooling. @@ -13,7 +13,7 @@ Account operations are sequential on a single thread, and no concurrency or "rac [free-threading]: https://docs.python.org/3/howto/free-threading-python.html [threading-module]: https://docs.python.org/3/library/threading.html#module-threading [multiprocessing-module]: https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes -```` +~~~~
@@ -38,4 +38,4 @@ raise ValueError('amount must be greater than 0') # withdrawal is too big raise ValueError('amount must be less than balance') -``` \ No newline at end of file +``` From 07c811525c30664f46addc896846141bcdb3676b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 20 Feb 2025 19:41:12 -0800 Subject: [PATCH 777/826] [Resistor Color Expert]: Fixed up Instructions & Introduction (#3873) * Fixed up instructions and introduction with exercise links and normalized color names, * Further clarifications for list input. * final typo fixes. --- .../.docs/instructions.md | 67 ++++++++++--------- .../.docs/introduction.md | 6 +- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/exercises/practice/resistor-color-expert/.docs/instructions.md b/exercises/practice/resistor-color-expert/.docs/instructions.md index 7a110832c8..96b9827446 100644 --- a/exercises/practice/resistor-color-expert/.docs/instructions.md +++ b/exercises/practice/resistor-color-expert/.docs/instructions.md @@ -1,36 +1,36 @@ # Instructions In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. -The program will take 1, 4, or 5 colors as input, and outputs the correct value, in ohms. +The program will take 1, 4, or 5 colors as input and output the correct value in ohms. The color bands are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 - -In `resistor-color trio` you decoded the first three colors. +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 + +In [`Resistor Color Trio`][resistor-color-trio-exercise] you decoded the first three color bands. For instance: orange-orange-brown translated to the main value `330`. In this exercise you will need to add _tolerance_ to the mix. Tolerance is the maximum amount that a value can be above or below the main value. -For example, if the last band is green, the maximum tolerance will be ±0.5%. +For example, if the last band is green, the maximum tolerance will be `±0.5%`. The tolerance band will have one of these values: -- Grey - 0.05% -- Violet - 0.1% -- Blue - 0.25% -- Green - 0.5% -- Brown - 1% -- Red - 2% -- Gold - 5% -- Silver - 10% +- grey - 0.05% +- violet - 0.1% +- blue - 0.25% +- green - 0.5% +- brown - 1% +- red - 2% +- gold - 5% +- silver - 10% The four-band resistor is built up like this: @@ -38,10 +38,10 @@ The four-band resistor is built up like this: | ------- | ------- | ---------- | --------- | | Value_1 | Value_2 | Multiplier | Tolerance | -Meaning +Examples: -- orange-orange-brown-green would be 330 ohms with a ±0.5% tolerance. -- orange-orange-red-grey would be 3300 ohms with ±0.05% tolerance. +- orange-orange-brown-green would be `330` ohms with a `±0.5%` tolerance. +- orange-orange-red-grey would be `3300` ohms with `±0.05%` tolerance. The difference between a four and five-band resistor is that the five-band resistor has an extra band to indicate a more precise value. @@ -49,31 +49,34 @@ The difference between a four and five-band resistor is that the five-band resis | ------- | ------- | ------- | ---------- | --------- | | Value_1 | Value_2 | Value_3 | Multiplier | Tolerance | -Meaning +Examples: -- orange-orange-orange-black-green would be 333 ohms with a ±0.5% tolerance. -- orange-red-orange-blue-violet would be 323M ohms with a ±0.10 tolerance. +- orange-orange-orange-black-green would be `333` ohms with a `±0.5%` tolerance. +- orange-red-orange-blue-violet would be `323M` ohms with a `±0.10` tolerance. There are also one band resistors. One band resistors only have the color black with a value of 0. -This exercise is about translating the resistor band colors into a label: + +Your program should translate an input `list` of resistor band colors into a label: "... ohms ...%" -So an input of "orange", "orange", "black", "green" should return: +So an input `list` of `["orange", "orange", "black", "green"]` should return: "33 ohms ±0.5%" When there are more than a thousand ohms, we say "kiloohms". That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. -So an input of "orange", "orange", "orange", "grey" should return: +So an input `list` of `["orange", "orange", "orange", "grey"]` should return: "33 kiloohms ±0.05%" When there are more than a million ohms, we say "megaohms". -So an input of "orange", "orange", "blue", "red" should return: +So an input `list` of `["orange", "orange", "blue", "red"]` should return: "33 megaohms ±2%" + +[resistor-color-trio-exercise]: https://exercism.org/tracks/python/exercises/resistor-color-trio diff --git a/exercises/practice/resistor-color-expert/.docs/introduction.md b/exercises/practice/resistor-color-expert/.docs/introduction.md index fd9e05efc4..868b03c534 100644 --- a/exercises/practice/resistor-color-expert/.docs/introduction.md +++ b/exercises/practice/resistor-color-expert/.docs/introduction.md @@ -1,10 +1,14 @@ # Introduction If you want to build something using a Raspberry Pi, you'll probably use _resistors_. -Like the previous `Resistor Color Duo` and `Resistor Color Trio` exercises, you will be translating resistor color bands to human-readable labels. +Like the previous [`Resistor Color Duo`][resistor-color-duo-exercise] and [`Resistor Color Trio`][resistor-color-trio-exercise] exercises, you will be translating resistor color bands to human-readable labels. - Each resistor has a resistance value. - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. - Each band acts as a digit of a number. For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. + + +[resistor-color-duo-exercise]: https://exercism.org/tracks/python/exercises/resistor-color-duo +[resistor-color-trio-exercise]: https://exercism.org/tracks/python/exercises/resistor-color-trio From f3e76771846d060d172e44612c8bac5c0112de60 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 20 Feb 2025 20:36:01 -0800 Subject: [PATCH 778/826] [Mecha Munch Management]: Fix Typo in instructions.md (#3874) `'Apple': 1` was missing from results dict on Task 3 examples. --- exercises/concept/mecha-munch-management/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index 802366aab4..7681547069 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -62,7 +62,7 @@ The function should return the new/updated "ideas" dictionary. (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)) ... -{'Banana Bread' : {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, +{'Banana Bread' : {'Banana': 4, 'Apple': 1, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}} >>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, From 9dcd000581cc5b8226ff013961d956f756c666ff Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 22 Mar 2025 11:49:06 -0700 Subject: [PATCH 779/826] Synced metadata for practice exercises for March. (#3879) Problem Spec sync. --- exercises/practice/grains/.meta/config.json | 2 +- exercises/practice/leap/.meta/config.json | 2 +- exercises/practice/rna-transcription/.meta/config.json | 2 +- exercises/practice/say/.meta/config.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json index 4e59df7431..00ca9f18d1 100644 --- a/exercises/practice/grains/.meta/config.json +++ b/exercises/practice/grains/.meta/config.json @@ -30,5 +30,5 @@ }, "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", "source": "The CodeRanch Cattle Drive, Assignment 6", - "source_url": "https://coderanch.com/wiki/718824/Grains" + "source_url": "https://web.archive.org/web/20240908084142/https://coderanch.com/wiki/718824/Grains" } diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index 1c35f22be1..2e838e97b4 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -34,5 +34,5 @@ }, "blurb": "Determine whether a given year is a leap year.", "source": "CodeRanch Cattle Drive, Assignment 3", - "source_url": "https://coderanch.com/t/718816/Leap" + "source_url": "https://web.archive.org/web/20240907033714/https://coderanch.com/t/718816/Leap" } diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index 636aa7ed31..090e578177 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -32,7 +32,7 @@ ".meta/example.py" ] }, - "blurb": "Given a DNA strand, return its RNA Complement Transcription.", + "blurb": "Given a DNA strand, return its RNA complement.", "source": "Hyperphysics", "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" } diff --git a/exercises/practice/say/.meta/config.json b/exercises/practice/say/.meta/config.json index 1090a04a47..ec2336bd98 100644 --- a/exercises/practice/say/.meta/config.json +++ b/exercises/practice/say/.meta/config.json @@ -30,5 +30,5 @@ }, "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", "source": "A variation on the JavaRanch CattleDrive, Assignment 4", - "source_url": "https://coderanch.com/wiki/718804" + "source_url": "https://web.archive.org/web/20240907035912/https://coderanch.com/wiki/718804" } From f3a33846a201427d9cabf47ff9457e70b331cec2 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 22 Mar 2025 11:49:38 -0700 Subject: [PATCH 780/826] March sync of practice exercise docs. (#3880) Problem Spec Sync. --- .../affine-cipher/.docs/instructions.md | 2 +- .../flatten-array/.docs/instructions.md | 15 ++-- .../flatten-array/.docs/introduction.md | 7 ++ .../practice/grains/.docs/instructions.md | 14 ++-- .../practice/grains/.docs/introduction.md | 6 ++ .../saddle-points/.docs/instructions.md | 11 +-- .../practice/sieve/.docs/instructions.md | 75 +++++++++++++++++-- .../simple-cipher/.docs/instructions.md | 10 +-- 8 files changed, 107 insertions(+), 33 deletions(-) create mode 100644 exercises/practice/flatten-array/.docs/introduction.md create mode 100644 exercises/practice/grains/.docs/introduction.md diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index f6329db936..1603dbbce9 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -20,7 +20,7 @@ Where: - `i` is the letter's index from `0` to the length of the alphabet - 1. - `m` is the length of the alphabet. - For the Roman alphabet `m` is `26`. + For the Latin alphabet `m` is `26`. - `a` and `b` are integers which make up the encryption key. Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). diff --git a/exercises/practice/flatten-array/.docs/instructions.md b/exercises/practice/flatten-array/.docs/instructions.md index 89dacfa327..b5b82713d9 100644 --- a/exercises/practice/flatten-array/.docs/instructions.md +++ b/exercises/practice/flatten-array/.docs/instructions.md @@ -1,11 +1,16 @@ # Instructions -Take a nested list and return a single flattened list with all values except nil/null. +Take a nested array of any depth and return a fully flattened array. -The challenge is to take an arbitrarily-deep nested list-like structure and produce a flattened structure without any nil/null values. +Note that some language tracks may include null-like values in the input array, and the way these values are represented varies by track. +Such values should be excluded from the flattened array. -For example: +Additionally, the input may be of a different data type and contain different types, depending on the track. -input: [1,[2,3,null,4],[null],5] +Check the test suite for details. -output: [1,2,3,4,5] +## Example + +input: `[1, [2, 6, null], [[null, [4]], 5]]` + +output: `[1, 2, 6, 4, 5]` diff --git a/exercises/practice/flatten-array/.docs/introduction.md b/exercises/practice/flatten-array/.docs/introduction.md new file mode 100644 index 0000000000..a314857465 --- /dev/null +++ b/exercises/practice/flatten-array/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +A shipment of emergency supplies has arrived, but there's a problem. +To protect from damage, the items — flashlights, first-aid kits, blankets — are packed inside boxes, and some of those boxes are nested several layers deep inside other boxes! + +To be prepared for an emergency, everything must be easily accessible in one box. +Can you unpack all the supplies and place them into a single box, so they're ready when needed most? diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md index df479fc0a1..f5b752a817 100644 --- a/exercises/practice/grains/.docs/instructions.md +++ b/exercises/practice/grains/.docs/instructions.md @@ -1,15 +1,11 @@ # Instructions -Calculate the number of grains of wheat on a chessboard given that the number on each square doubles. +Calculate the number of grains of wheat on a chessboard. -There once was a wise servant who saved the life of a prince. -The king promised to pay whatever the servant could dream up. -Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. -One grain on the first square of a chess board, with the number of grains doubling on each successive square. +A chessboard has 64 squares. +Square 1 has one grain, square 2 has two grains, square 3 has four grains, and so on, doubling each time. -There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). +Write code that calculates: -Write code that shows: - -- how many grains were on a given square, and +- the number of grains on a given square - the total number of grains on the chessboard diff --git a/exercises/practice/grains/.docs/introduction.md b/exercises/practice/grains/.docs/introduction.md new file mode 100644 index 0000000000..0df4f46f72 --- /dev/null +++ b/exercises/practice/grains/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chessboard, with the number of grains doubling on each successive square. diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index c585568b46..f69cdab958 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -13,11 +13,12 @@ Or it might have one, or even several. Here is a grid that has exactly one candidate tree. ```text - 1 2 3 4 - |----------- -1 | 9 8 7 8 -2 | 5 3 2 4 <--- potential tree house at row 2, column 1, for tree with height 5 -3 | 6 6 7 1 + ↓ + 1 2 3 4 + |----------- + 1 | 9 8 7 8 +→ 2 |[5] 3 2 4 + 3 | 6 6 7 1 ``` - Row 2 has values 5, 3, 2, and 4. The largest value is 5. diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 085c0a57d9..71292e1782 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -6,37 +6,96 @@ A prime number is a number larger than 1 that is only divisible by 1 and itself. For example, 2, 3, 5, 7, 11, and 13 are prime numbers. By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. -To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. -Then you repeat the following steps: +To use the Sieve of Eratosthenes, first, write out all the numbers from 2 up to and including your given number. +Then, follow these steps: -1. Find the next unmarked number in your list (skipping over marked numbers). +1. Find the next unmarked number (skipping over marked numbers). This is a prime number. 2. Mark all the multiples of that prime number as **not** prime. -You keep repeating these steps until you've gone through every number in your list. +Repeat the steps until you've gone through every number. At the end, all the unmarked numbers are prime. ~~~~exercism/note -The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. -To check you are implementing the Sieve correctly, a good first test is to check that you do not use division or remainder operations. +The Sieve of Eratosthenes marks off multiples of each prime using addition (repeatedly adding the prime) or multiplication (directly computing its multiples), rather than checking each number for divisibility. + +The tests don't check that you've implemented the algorithm, only that you've come up with the correct primes. ~~~~ ## Example Let's say you're finding the primes less than or equal to 10. -- List out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. +- Write out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. + + ```text + 2 3 4 5 6 7 8 9 10 + ``` + - 2 is unmarked and is therefore a prime. Mark 4, 6, 8 and 10 as "not prime". + + ```text + 2 3 [4] 5 [6] 7 [8] 9 [10] + ↑ + ``` + - 3 is unmarked and is therefore a prime. Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 4 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 5 is unmarked and is therefore a prime. Mark 10 as not prime _(optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 6 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 7 is unmarked and is therefore a prime. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 8 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 9 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 10 is marked as "not prime", so we stop as there are no more numbers to check. -You've examined all numbers and found 2, 3, 5, and 7 are still unmarked, which means they're the primes less than or equal to 10. + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +You've examined all the numbers and found that 2, 3, 5, and 7 are still unmarked, meaning they're the primes less than or equal to 10. diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 475af61828..337857442a 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -11,14 +11,14 @@ If anyone wishes to decipher these, and get at their meaning, he must substitute Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts. -The Caesar Cipher was used for some messages from Julius Caesar that were sent afield. +The Caesar cipher was used for some messages from Julius Caesar that were sent afield. Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know. -Your task is to create a simple shift cipher like the Caesar Cipher. -This image is a great example of the Caesar Cipher: +Your task is to create a simple shift cipher like the Caesar cipher. +This image is a great example of the Caesar cipher: -![Caesar Cipher][img-caesar-cipher] +![Caesar cipher][img-caesar-cipher] For example: @@ -44,7 +44,7 @@ would return the obscured "ldpdsdqgdehdu" In the example above, we've set a = 0 for the key value. So when the plaintext is added to the key, we end up with the same message coming out. So "aaaa" is not an ideal key. -But if we set the key to "dddd", we would get the same thing as the Caesar Cipher. +But if we set the key to "dddd", we would get the same thing as the Caesar cipher. ## Step 3 From e348197fc0be61ade0a345ec78e2476b2c5a7210 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 22 Mar 2025 11:50:30 -0700 Subject: [PATCH 781/826] Updated tests and regenerated test cases for Flatten Array. (#3881) While the example didn't need to be altered, the test cases changed enough, I think we need to re-run these. --- .../practice/flatten-array/.meta/tests.toml | 20 +++++++++++++++++++ .../flatten-array/flatten_array_test.py | 10 +++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/exercises/practice/flatten-array/.meta/tests.toml b/exercises/practice/flatten-array/.meta/tests.toml index 6300219d71..44acf175d2 100644 --- a/exercises/practice/flatten-array/.meta/tests.toml +++ b/exercises/practice/flatten-array/.meta/tests.toml @@ -32,12 +32,32 @@ description = "null values are omitted from the final result" [c6cf26de-8ccd-4410-84bd-b9efd88fd2bc] description = "consecutive null values at the front of the list are omitted from the final result" +include = false + +[bc72da10-5f55-4ada-baf3-50e4da02ec8e] +description = "consecutive null values at the front of the array are omitted from the final result" +reimplements = "c6cf26de-8ccd-4410-84bd-b9efd88fd2bc" [382c5242-587e-4577-b8ce-a5fb51e385a1] description = "consecutive null values in the middle of the list are omitted from the final result" +include = false + +[6991836d-0d9b-4703-80a0-3f1f23eb5981] +description = "consecutive null values in the middle of the array are omitted from the final result" +reimplements = "382c5242-587e-4577-b8ce-a5fb51e385a1" [ef1d4790-1b1e-4939-a179-51ace0829dbd] description = "6 level nest list with null values" +include = false + +[dc90a09c-5376-449c-a7b3-c2d20d540069] +description = "6 level nested array with null values" +reimplements = "ef1d4790-1b1e-4939-a179-51ace0829dbd" [85721643-705a-4150-93ab-7ae398e2942d] description = "all values in nested list are null" +include = false + +[51f5d9af-8f7f-4fb5-a156-69e8282cb275] +description = "all values in nested array are null" +reimplements = "85721643-705a-4150-93ab-7ae398e2942d" diff --git a/exercises/practice/flatten-array/flatten_array_test.py b/exercises/practice/flatten-array/flatten_array_test.py index cecb3c5633..8cd077d9ad 100644 --- a/exercises/practice/flatten-array/flatten_array_test.py +++ b/exercises/practice/flatten-array/flatten_array_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flatten-array/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-03-22 import unittest @@ -45,26 +45,26 @@ def test_null_values_are_omitted_from_the_final_result(self): expected = [1, 2] self.assertEqual(flatten(inputs), expected) - def test_consecutive_null_values_at_the_front_of_the_list_are_omitted_from_the_final_result( + def test_consecutive_null_values_at_the_front_of_the_array_are_omitted_from_the_final_result( self, ): inputs = [None, None, 3] expected = [3] self.assertEqual(flatten(inputs), expected) - def test_consecutive_null_values_in_the_middle_of_the_list_are_omitted_from_the_final_result( + def test_consecutive_null_values_in_the_middle_of_the_array_are_omitted_from_the_final_result( self, ): inputs = [1, None, None, 4] expected = [1, 4] self.assertEqual(flatten(inputs), expected) - def test_6_level_nest_list_with_null_values(self): + def test_6_level_nested_array_with_null_values(self): inputs = [0, 2, [[2, 3], 8, [[100]], None, [[None]]], -2] expected = [0, 2, 2, 3, 8, 100, -2] self.assertEqual(flatten(inputs), expected) - def test_all_values_in_nested_list_are_null(self): + def test_all_values_in_nested_array_are_null(self): inputs = [None, [[[None]]], None, None, [[None, None], None], None] expected = [] self.assertEqual(flatten(inputs), expected) From 9b7a74b59eb82a65d926532f4590af3eb6861c30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:25:23 -0700 Subject: [PATCH 782/826] Bump actions/setup-python from 5.4.0 to 5.5.0 (#3882) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.4.0 to 5.5.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/42375524e23c412d93fb67b49958b491fce71c38...8d9ed9ac5c53483de85588cdf95a591a75ab9f55) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 75154ccd5e..1e1f73a217 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Python - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 + uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 with: python-version: ${{ matrix.python-version }} From 085aeb1dbf3bcb8406596214c4dc3bb9e087ecf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 19:19:06 -0700 Subject: [PATCH 783/826] Bump actions/setup-python from 5.5.0 to 5.6.0 (#3910) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.5.0 to 5.6.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/8d9ed9ac5c53483de85588cdf95a591a75ab9f55...a26af69be951a213d495a4c3e4e4022e16d87065) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 5.6.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 1e1f73a217..8764211cbe 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Python - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 with: python-version: ${{ matrix.python-version }} From fad4221ce9c7eab92c9e3b240facbde82a143c70 Mon Sep 17 00:00:00 2001 From: maekki Date: Sun, 1 Jun 2025 20:41:42 +0200 Subject: [PATCH 784/826] Fix example for just_the_buzz (#3914) --- concepts/conditionals/about.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index d0b91f26ce..2060905b33 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -141,8 +141,8 @@ def just_the_buzz(number): >>> just_the_buzz(15) 'Buzz!' ->>> just_the_buzz(10) -'10' +>>> just_the_buzz(7) +'7' ``` ## Truthy and Falsy From 00c2497fa7302bde03869ef0eb7e4a28e7907dc6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 20 Jun 2025 17:41:45 -0700 Subject: [PATCH 785/826] Synced practice exercise instructions to problem specifications. (#3920) --- .../binary-search-tree/.docs/instructions.md | 23 ++++++ .../practice/dot-dsl/.docs/instructions.md | 2 +- exercises/practice/luhn/.docs/instructions.md | 45 ++++++----- .../practice/meetup/.docs/instructions.md | 2 +- .../phone-number/.docs/instructions.md | 2 +- .../protein-translation/.docs/instructions.md | 47 +++++------ .../simple-cipher/.docs/instructions.md | 78 +++++++------------ 7 files changed, 97 insertions(+), 102 deletions(-) diff --git a/exercises/practice/binary-search-tree/.docs/instructions.md b/exercises/practice/binary-search-tree/.docs/instructions.md index c9bbba5b96..7625220e9a 100644 --- a/exercises/practice/binary-search-tree/.docs/instructions.md +++ b/exercises/practice/binary-search-tree/.docs/instructions.md @@ -19,29 +19,52 @@ All data in the left subtree is less than or equal to the current node's data, a For example, if we had a node containing the data 4, and we added the data 2, our tree would look like this: +![A graph with root node 4 and a single child node 2.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2.svg) + +```text 4 / 2 +``` If we then added 6, it would look like this: +![A graph with root node 4 and two child nodes 2 and 6.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6.svg) + +```text 4 / \ 2 6 +``` If we then added 3, it would look like this +![A graph with root node 4, two child nodes 2 and 6, and a grandchild node 3.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-3.svg) + +```text 4 / \ 2 6 \ 3 +``` And if we then added 1, 5, and 7, it would look like this +![A graph with root node 4, two child nodes 2 and 6, and four grandchild nodes 1, 3, 5 and 7.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-1-3-5-7.svg) + +```text 4 / \ / \ 2 6 / \ / \ 1 3 5 7 +``` + +## Credit + +The images were created by [habere-et-dispertire][habere-et-dispertire] using [PGF/TikZ][pgf-tikz] by Till Tantau. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[pgf-tikz]: https://en.wikipedia.org/wiki/PGF/TikZ diff --git a/exercises/practice/dot-dsl/.docs/instructions.md b/exercises/practice/dot-dsl/.docs/instructions.md index b3a63996d8..5e65ebef94 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.md +++ b/exercises/practice/dot-dsl/.docs/instructions.md @@ -22,7 +22,7 @@ Write a Domain Specific Language similar to the Graphviz dot language. Our DSL is similar to the Graphviz dot language in that our DSL will be used to create graph data structures. However, unlike the DOT Language, our DSL will be an internal DSL for use only in our language. -More information about the difference between internal and external DSLs can be found [here][fowler-dsl]. +[Learn more about the difference between internal and external DSLs][fowler-dsl]. [dsl]: https://en.wikipedia.org/wiki/Domain-specific_language [dot-language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language) diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 5bbf007b07..df2e304a39 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Determine whether a credit card number is valid according to the [Luhn formula][luhn]. +Determine whether a number is valid according to the [Luhn formula][luhn]. The number will be provided as a string. @@ -10,54 +10,59 @@ Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. All other non-digit characters are disallowed. -### Example 1: valid credit card number +## Examples -```text -4539 3195 0343 6467 -``` +### Valid credit card number -The first step of the Luhn algorithm is to double every second digit, starting from the right. -We will be doubling +The number to be checked is `4539 3195 0343 6467`. + +The first step of the Luhn algorithm is to start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text 4539 3195 0343 6467 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` -If doubling the number results in a number greater than 9 then subtract 9 from the product. -The results of our doubling: +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text 8569 6195 0383 3437 ``` -Then sum all of the digits: +Finally, we sum all digits. +If the sum is evenly divisible by 10, the original number is valid. ```text -8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80 +8 + 5 + 6 + 9 + 6 + 1 + 9 + 5 + 0 + 3 + 8 + 3 + 3 + 4 + 3 + 7 = 80 ``` -If the sum is evenly divisible by 10, then the number is valid. -This number is valid! +80 is evenly divisible by 10, so number `4539 3195 0343 6467` is valid! + +### Invalid Canadian SIN + +The number to be checked is `066 123 468`. -### Example 2: invalid credit card number +We start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text -8273 1232 7352 0569 +066 123 478 + ↑ ↑ ↑ ↑ (double these) ``` -Double the second digits, starting from the right +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text -7253 2262 5312 0539 +036 226 458 ``` -Sum the digits +We sum the digits: ```text -7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57 +0 + 3 + 6 + 2 + 2 + 6 + 4 + 5 + 8 = 36 ``` -57 is not evenly divisible by 10, so this number is not valid. +36 is not evenly divisible by 10, so number `066 123 478` is not valid! [luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm diff --git a/exercises/practice/meetup/.docs/instructions.md b/exercises/practice/meetup/.docs/instructions.md index 000de2fd12..8b1bda5eb4 100644 --- a/exercises/practice/meetup/.docs/instructions.md +++ b/exercises/practice/meetup/.docs/instructions.md @@ -2,7 +2,7 @@ Your task is to find the exact date of a meetup, given a month, year, weekday and week. -There are five week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`. +There are six week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`. For example, you might be asked to find the date for the meetup on the first Monday in January 2018 (January 1, 2018). diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 62ba48e96f..5d4d3739f4 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Clean up user-entered phone numbers so that they can be sent SMS messages. +Clean up phone numbers so that they can be sent SMS messages. The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index 44880802c5..35c953b11f 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -1,36 +1,17 @@ # Instructions -Translate RNA sequences into proteins. +Your job is to translate RNA sequences into proteins. -RNA can be broken into three-nucleotide sequences called codons, and then translated to a protein like so: +RNA strands are made up of three-nucleotide sequences called **codons**. +Each codon translates to an **amino acid**. +When joined together, those amino acids make a protein. -RNA: `"AUGUUUUCU"` => translates to - -Codons: `"AUG", "UUU", "UCU"` -=> which become a protein with the following sequence => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. -If it works for one codon, the program should work for all of them. -However, feel free to expand the list in the test suite to include them all. - -There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated. - -All subsequent codons after are ignored, like this: - -RNA: `"AUGUUUUCUUAAAUG"` => - -Codons: `"AUG", "UUU", "UCU", "UAA", "AUG"` => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. - -Below are the codons and resulting amino acids needed for the exercise. +In the real world, there are 64 codons, which in turn correspond to 20 amino acids. +However, for this exercise, you’ll only use a few of the possible 64. +They are listed below: | Codon | Amino Acid | -| :----------------- | :------------ | +| ------------------ | ------------- | | AUG | Methionine | | UUU, UUC | Phenylalanine | | UUA, UUG | Leucine | @@ -40,6 +21,18 @@ Below are the codons and resulting amino acids needed for the exercise. | UGG | Tryptophan | | UAA, UAG, UGA | STOP | +For example, the RNA string “AUGUUUUCU” has three codons: “AUG”, “UUU” and “UCU”. +These map to Methionine, Phenylalanine, and Serine. + +## “STOP” Codons + +You’ll note from the table above that there are three **“STOP” codons**. +If you encounter any of these codons, ignore the rest of the sequence — the protein is complete. + +For example, “AUGUUUUCUUAAAUG” contains a STOP codon (“UAA”). +Once we reach that point, we stop processing. +We therefore only consider the part before it (i.e. “AUGUUUUCU”), not any further codons after it (i.e. “AUG”). + Learn more about [protein translation on Wikipedia][protein-translation]. [protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 337857442a..afd0b57da9 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -1,66 +1,40 @@ # Instructions -Implement a simple shift cipher like Caesar and a more secure substitution cipher. +Create an implementation of the [Vigenère cipher][wiki]. +The Vigenère cipher is a simple substitution cipher. -## Step 1 +## Cipher terminology -"If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out. -If anyone wishes to decipher these, and get at their meaning, he must substitute the fourth letter of the alphabet, namely D, for A, and so with the others." -—Suetonius, Life of Julius Caesar +A cipher is an algorithm used to encrypt, or encode, a string. +The unencrypted string is called the _plaintext_ and the encrypted string is called the _ciphertext_. +Converting plaintext to ciphertext is called _encoding_ while the reverse is called _decoding_. -Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. -They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts. +In a _substitution cipher_, each plaintext letter is replaced with a ciphertext letter which is computed with the help of a _key_. +(Note, it is possible for replacement letter to be the same as the original letter.) -The Caesar cipher was used for some messages from Julius Caesar that were sent afield. -Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. -So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know. +## Encoding details -Your task is to create a simple shift cipher like the Caesar cipher. -This image is a great example of the Caesar cipher: +In this cipher, the key is a series of lowercase letters, such as `"abcd"`. +Each letter of the plaintext is _shifted_ or _rotated_ by a distance based on a corresponding letter in the key. +An `"a"` in the key means a shift of 0 (that is, no shift). +A `"b"` in the key means a shift of 1. +A `"c"` in the key means a shift of 2, and so on. -![Caesar cipher][img-caesar-cipher] +The first letter of the plaintext uses the first letter of the key, the second letter of the plaintext uses the second letter of the key and so on. +If you run out of letters in the key before you run out of letters in the plaintext, start over from the start of the key again. -For example: +If the key only contains one letter, such as `"dddddd"`, then all letters of the plaintext are shifted by the same amount (three in this example), which would make this the same as a rotational cipher or shift cipher (sometimes called a Caesar cipher). +For example, the plaintext `"iamapandabear"` would become `"ldpdsdqgdehdu"`. -Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". -Obscure enough to keep our message secret in transit. +If the key only contains the letter `"a"` (one or more times), the shift distance is zero and the ciphertext is the same as the plaintext. -When "ldpdsdqgdehdu" is put into the decode function it would return the original "iamapandabear" letting your friend read your original message. +Usually the key is more complicated than that, though! +If the key is `"abcd"` then letters of the plaintext would be shifted by a distance of 0, 1, 2, and 3. +If the plaintext is `"hello"`, we need 5 shifts so the key would wrap around, giving shift distances of 0, 1, 2, 3, and 0. +Applying those shifts to the letters of `"hello"` we get `"hfnoo"`. -## Step 2 +## Random keys -Shift ciphers quickly cease to be useful when the opposition commander figures them out. -So instead, let's try using a substitution cipher. -Try amending the code to allow us to specify a key and use that for the shift distance. +If no key is provided, generate a key which consists of at least 100 random lowercase letters from the Latin alphabet. -Here's an example: - -Given the key "aaaaaaaaaaaaaaaaaa", encoding the string "iamapandabear" -would return the original "iamapandabear". - -Given the key "ddddddddddddddddd", encoding our string "iamapandabear" -would return the obscured "ldpdsdqgdehdu" - -In the example above, we've set a = 0 for the key value. -So when the plaintext is added to the key, we end up with the same message coming out. -So "aaaa" is not an ideal key. -But if we set the key to "dddd", we would get the same thing as the Caesar cipher. - -## Step 3 - -The weakest link in any cipher is the human being. -Let's make your substitution cipher a little more fault tolerant by providing a source of randomness and ensuring that the key contains only lowercase letters. - -If someone doesn't submit a key at all, generate a truly random key of at least 100 lowercase characters in length. - -## Extensions - -Shift ciphers work by making the text slightly odd, but are vulnerable to frequency analysis. -Substitution ciphers help that, but are still very vulnerable when the key is short or if spaces are preserved. -Later on you'll see one solution to this problem in the exercise "crypto-square". - -If you want to go farther in this field, the questions begin to be about how we can exchange keys in a secure way. -Take a look at [Diffie-Hellman on Wikipedia][dh] for one of the first implementations of this scheme. - -[img-caesar-cipher]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png -[dh]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +[wiki]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher From 11c804d258f0b3321598e17f6eec7224f0e7b5fe Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 20 Jun 2025 17:59:23 -0700 Subject: [PATCH 786/826] [June Problem-Spec Sync]: Practice Exercise Introductions (#3921) * Synced practice exercise introductions to problem specifications. --- exercises/practice/eliuds-eggs/.docs/introduction.md | 2 +- exercises/practice/luhn/.docs/introduction.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md index 8198974809..2b2e5c43d8 100644 --- a/exercises/practice/eliuds-eggs/.docs/introduction.md +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -58,7 +58,7 @@ The position information encoding is calculated as follows: ### Decimal number on the display -16 +8 ### Actual eggs in the coop diff --git a/exercises/practice/luhn/.docs/introduction.md b/exercises/practice/luhn/.docs/introduction.md index ec2bd709d2..dee48006ed 100644 --- a/exercises/practice/luhn/.docs/introduction.md +++ b/exercises/practice/luhn/.docs/introduction.md @@ -2,10 +2,10 @@ At the Global Verification Authority, you've just been entrusted with a critical assignment. Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. -The Luhn algorithm is a simple checksum formula used to ensure these numbers are valid and error-free. +The Luhn algorithm is a simple checksum formula used to help identify mistyped numbers. A batch of identifiers has just arrived on your desk. All of them must pass the Luhn test to ensure they're legitimate. -If any fail, they'll be flagged as invalid, preventing errors or fraud, such as incorrect transactions or unauthorized access. +If any fail, they'll be flagged as invalid, preventing mistakes such as incorrect transactions or failed account verifications. Can you ensure this is done right? The integrity of many services depends on you. From a1205c5bfd449f4903a156e89aaecd9525f07b44 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 20 Jun 2025 18:00:00 -0700 Subject: [PATCH 787/826] Synced practice exercise metadata to problem specifications. (#3922) --- exercises/practice/simple-cipher/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index 0dc1687acf..ced62d9926 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -30,7 +30,7 @@ ".meta/example.py" ] }, - "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher.", + "blurb": "Implement the Vigenère cipher, a simple substitution cipher.", "source": "Substitution Cipher at Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" } From b60e2437e18329459da3f26fc750f9879d811178 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 20 Jun 2025 18:00:52 -0700 Subject: [PATCH 788/826] Synced tests toml and regenerated test file for crypto-square. (#3923) [no important files changed] --- exercises/practice/crypto-square/.meta/tests.toml | 5 +++++ exercises/practice/crypto-square/crypto_square_test.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/exercises/practice/crypto-square/.meta/tests.toml b/exercises/practice/crypto-square/.meta/tests.toml index 085d142ead..94ef0819fe 100644 --- a/exercises/practice/crypto-square/.meta/tests.toml +++ b/exercises/practice/crypto-square/.meta/tests.toml @@ -32,3 +32,8 @@ description = "8 character plaintext results in 3 chunks, the last one with a tr [fbcb0c6d-4c39-4a31-83f6-c473baa6af80] description = "54 character plaintext results in 7 chunks, the last two with trailing spaces" +include = false + +[33fd914e-fa44-445b-8f38-ff8fbc9fe6e6] +description = "54 character plaintext results in 8 chunks, the last two with trailing spaces" +reimplements = "fbcb0c6d-4c39-4a31-83f6-c473baa6af80" diff --git a/exercises/practice/crypto-square/crypto_square_test.py b/exercises/practice/crypto-square/crypto_square_test.py index 97630a6750..5703ccd819 100644 --- a/exercises/practice/crypto-square/crypto_square_test.py +++ b/exercises/practice/crypto-square/crypto_square_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-06-20 import unittest @@ -47,7 +47,7 @@ def test_8_character_plaintext_results_in_3_chunks_the_last_one_with_a_trailing_ expected = "clu hlt io " self.assertEqual(cipher_text(value), expected) - def test_54_character_plaintext_results_in_7_chunks_the_last_two_with_trailing_spaces( + def test_54_character_plaintext_results_in_8_chunks_the_last_two_with_trailing_spaces( self, ): value = "If man was meant to stay on the ground, god would have given us roots." From 184bbc28d8d2b4e444d4d3a2612f07867c61ed92 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 20 Jun 2025 18:01:37 -0700 Subject: [PATCH 789/826] Synced tests toml, regenerated test cases, and updated example for largest series product. (#3924) --- .../practice/largest-series-product/.meta/example.py | 4 ++-- .../practice/largest-series-product/.meta/tests.toml | 10 ++++++++++ .../largest_series_product_test.py | 10 +++------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/exercises/practice/largest-series-product/.meta/example.py b/exercises/practice/largest-series-product/.meta/example.py index 0599e7417e..f7cfc2310b 100644 --- a/exercises/practice/largest-series-product/.meta/example.py +++ b/exercises/practice/largest-series-product/.meta/example.py @@ -5,7 +5,7 @@ def slices(series, size): if not size <= len(series): - raise ValueError('span must be smaller than string length') + raise ValueError('span must not exceed string length') elif not 0 < size: raise ValueError('span must not be negative') elif not all(item.isdigit() for item in series): @@ -20,4 +20,4 @@ def slices(series, size): def largest_product(series, size): if size == 0: return 1 - return max(reduce(mul, slice) for slice in slices(series, size)) + return max(reduce(mul, slice) for slice in slices(series, size)) \ No newline at end of file diff --git a/exercises/practice/largest-series-product/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml index 8831692597..982f517cc3 100644 --- a/exercises/practice/largest-series-product/.meta/tests.toml +++ b/exercises/practice/largest-series-product/.meta/tests.toml @@ -38,6 +38,11 @@ description = "reports zero if all spans include zero" [5d81aaf7-4f67-4125-bf33-11493cc7eab7] description = "rejects span longer than string length" +include = false + +[0ae1ce53-d9ba-41bb-827f-2fceb64f058b] +description = "rejects span longer than string length" +reimplements = "5d81aaf7-4f67-4125-bf33-11493cc7eab7" [06bc8b90-0c51-4c54-ac22-3ec3893a079e] description = "reports 1 for empty string and empty product (0 span)" @@ -49,6 +54,11 @@ include = false [6d96c691-4374-4404-80ee-2ea8f3613dd4] description = "rejects empty string and nonzero span" +include = false + +[6cf66098-a6af-4223-aab1-26aeeefc7402] +description = "rejects empty string and nonzero span" +reimplements = "6d96c691-4374-4404-80ee-2ea8f3613dd4" [7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74] description = "rejects invalid character in digits" diff --git a/exercises/practice/largest-series-product/largest_series_product_test.py b/exercises/practice/largest-series-product/largest_series_product_test.py index e505623673..494cd89138 100644 --- a/exercises/practice/largest-series-product/largest_series_product_test.py +++ b/exercises/practice/largest-series-product/largest_series_product_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/largest-series-product/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-06-20 import unittest @@ -44,17 +44,13 @@ def test_rejects_span_longer_than_string_length(self): with self.assertRaises(ValueError) as err: largest_product("123", 4) self.assertEqual(type(err.exception), ValueError) - self.assertEqual( - err.exception.args[0], "span must be smaller than string length" - ) + self.assertEqual(err.exception.args[0], "span must not exceed string length") def test_rejects_empty_string_and_nonzero_span(self): with self.assertRaises(ValueError) as err: largest_product("", 1) self.assertEqual(type(err.exception), ValueError) - self.assertEqual( - err.exception.args[0], "span must be smaller than string length" - ) + self.assertEqual(err.exception.args[0], "span must not exceed string length") def test_rejects_invalid_character_in_digits(self): with self.assertRaises(ValueError) as err: From f0125e4e5c44abab4defe2c9c41c166951cd1258 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 20 Jun 2025 18:02:20 -0700 Subject: [PATCH 790/826] Updated wordy tests toml and regenerated test file. (#3925) --- exercises/practice/wordy/.meta/tests.toml | 25 ++++++++++++++++++++--- exercises/practice/wordy/wordy_test.py | 14 ++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/exercises/practice/wordy/.meta/tests.toml b/exercises/practice/wordy/.meta/tests.toml index 1d0f9d92f0..52bdf919cc 100644 --- a/exercises/practice/wordy/.meta/tests.toml +++ b/exercises/practice/wordy/.meta/tests.toml @@ -1,13 +1,32 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [88bf4b28-0de3-4883-93c7-db1b14aa806e] description = "just a number" +[18983214-1dfc-4ebd-ac77-c110dde699ce] +description = "just a zero" + +[607c08ee-2241-4288-916d-dae5455c87e6] +description = "just a negative number" + [bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0] description = "addition" +[bb9f2082-171c-46ad-ad4e-c3f72087c1b5] +description = "addition with a left hand zero" + +[6fa05f17-405a-4742-80ae-5d1a8edb0d5d] +description = "addition with a right hand zero" + [79e49e06-c5ae-40aa-a352-7a3a01f70015] description = "more addition" diff --git a/exercises/practice/wordy/wordy_test.py b/exercises/practice/wordy/wordy_test.py index e9d01e9ef3..c34ed27cda 100644 --- a/exercises/practice/wordy/wordy_test.py +++ b/exercises/practice/wordy/wordy_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json -# File last updated on 2025-02-15 +# File last updated on 2025-06-20 import unittest @@ -13,9 +13,21 @@ class WordyTest(unittest.TestCase): def test_just_a_number(self): self.assertEqual(answer("What is 5?"), 5) + def test_just_a_zero(self): + self.assertEqual(answer("What is 0?"), 0) + + def test_just_a_negative_number(self): + self.assertEqual(answer("What is -123?"), -123) + def test_addition(self): self.assertEqual(answer("What is 1 plus 1?"), 2) + def test_addition_with_a_left_hand_zero(self): + self.assertEqual(answer("What is 0 plus 2?"), 2) + + def test_addition_with_a_right_hand_zero(self): + self.assertEqual(answer("What is 3 plus 0?"), 3) + def test_more_addition(self): self.assertEqual(answer("What is 53 plus 2?"), 55) From 4541ebe8d334a51c7ce4b83c0bf3568f4a2060c4 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 20 Jun 2025 18:08:19 -0700 Subject: [PATCH 791/826] [Wordy]: Updated Code in Approaches Docs (#3926) [no important files changed] * Updated code in wordy approaches for new test cases. --- .../.approaches/dunder-getattribute/content.md | 12 +++++++++--- .../import-callables-from-operator/content.md | 18 ++++++++++-------- .../practice/wordy/.approaches/introduction.md | 10 +++++++--- .../lambdas-in-a-dictionary/content.md | 4 ++-- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index 3f82c0ee51..167460f2d3 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -13,7 +13,11 @@ OPS = { def answer(question): question = question.removeprefix("What is").removesuffix("?").strip() if not question: raise ValueError("syntax error") - if question.isdigit(): return int(question) + + if question.startswith("-") and question[1:].isdigit(): + return -int(question[1:]) + elif question.isdigit(): + return int(question) found_op = False for name, op in OPS.items(): @@ -56,8 +60,9 @@ The method calls are [chained][method-chaining], so that the output from one cal If the input has no characters left, it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return a `ValueError("syntax error")`. -Next, the [`isdigit`][isdigit] method is used to see if the remaining characters in the input are digits. -If so, it uses the [`int()`][int-constructor] constructor to return the string as an integer. +Next, the [`str.startswith()`][startswith] and [`isdigit`][isdigit] methods are used to see if the remaining characters in the input are either negative or positive digits. +Because "-" is used to denote negative numbers, `str.startswith("-")` is used in the first condition and `question[1:].isdigit()` is then used for the remaining string. +If the `str.isdigit()` checks pass, the [`int()`][int-constructor] constructor is used to return the string as an integer with the proper sign. Next, the elements in the `OPS` dictionary are iterated over. If the key name is in the input, then the [`str.replace`][replace] method is used to replace the name in the input with the `dunder-method` value. @@ -94,5 +99,6 @@ When the loop exhausts, the first element of the list is selected as the functio [replace]: https://docs.python.org/3/library/stdtypes.html?#str.replace [split]: https://docs.python.org/3/library/stdtypes.html?#str.split [strip]: https://docs.python.org/3/library/stdtypes.html#str.strip +[startswith]: https://docs.python.org/3/library/stdtypes.html#str.startswith [unpacking]: https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/ [unpacking-and-multiple-assignment]: https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md index 9f57c1c9c6..9fdf3e20e0 100644 --- a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md +++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md @@ -5,22 +5,24 @@ from operator import add, mul, sub from operator import floordiv as div + OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + def answer(question): if not question.startswith("What is") or "cubed" in question: raise ValueError("unknown operation") - + question = question.removeprefix("What is").removesuffix("?").strip() - if question.isdigit(): - return int(question) - - if not question: + if not question: raise ValueError("syntax error") - + + if (question.startswith("-") and question[1:].isdigit()) or question.isdigit(): + return int(question) + equation = [word for word in question.split() if word != 'by'] - + while len(equation) > 1: try: x_value, operation, y_value, *rest = equation @@ -28,7 +30,7 @@ def answer(question): *rest] except: raise ValueError("syntax error") - + return equation[0] ``` diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index cc5e8031d9..821b122842 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -182,7 +182,7 @@ def answer(question): question = question.removeprefix("What is").removesuffix("?").strip() - if question.isdigit(): + if (question.startswith("-") and question[1:].isdigit()) or question.isdigit(): return int(question) if not question: @@ -286,7 +286,7 @@ def answer(question): question = question.removeprefix("What is").removesuffix("?").strip() - if question.isdigit(): + if (question.startswith("-") and question[1:].isdigit()) or question.isdigit(): return int(question) if not question: @@ -422,7 +422,11 @@ OPS = { def answer(question): question = question.removeprefix("What is").removesuffix("?").strip() if not question: raise ValueError("syntax error") - if question.isdigit(): return int(question) + + if question.startswith("-") and question[1:].isdigit(): + return -int(question[1:]) + elif question.isdigit(): + return int(question) found_op = False for name, op in OPS.items(): diff --git a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md index df06d3b171..d78f3e7db8 100644 --- a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md +++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md @@ -16,7 +16,7 @@ def answer(question): question = question.removeprefix("What is").removesuffix("?").strip() - if question.isdigit(): + if (question.startswith("-") and question[1:].isdigit()) or question.isdigit(): return int(question) if not question: @@ -67,7 +67,7 @@ def answer(question): question = question.removeprefix("What is").removesuffix("?").strip() - if question.isdigit(): + if (question.startswith("-") and question[1:].isdigit()) or question.isdigit(): return int(question) if not question: From 5b8462a9b638dec18c0b265d2a4affd05b5ec2d1 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 23 Jun 2025 14:07:13 -0700 Subject: [PATCH 792/826] Fix up generator link refs in Darts Approaches. (#3929) --- exercises/practice/darts/.approaches/introduction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/darts/.approaches/introduction.md b/exercises/practice/darts/.approaches/introduction.md index 588c266184..cf7c6a23dd 100644 --- a/exercises/practice/darts/.approaches/introduction.md +++ b/exercises/practice/darts/.approaches/introduction.md @@ -78,7 +78,7 @@ def score(x_coord, y_coord): ``` This approach is very similar to the [tuple and loop][approach-tuple-and-loop] approach, but iterates over [`dict.items()`][dict-items]. -For more information, see the [dict with generator-expression][approach-dict-with-generator-expression] approach. +For more information, see the [dict and generator][approach-dict-and-generator] approach. ## Approach: Using Boolean Values as Integers @@ -139,7 +139,7 @@ Although a strong argument could be made for simplicity and clarity — many lis [approach-boolean-values-as-integers]: https://exercism.org/tracks/python/exercises/darts/approaches/boolean-values-as-integers [approach-dict-and-dict-get]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-and-dict-get -[approach-dict-with-generator-expression]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-with-gnerator-expresson +[approach-dict-and-generator]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-and-generator [approach-if-statements ]: https://exercism.org/tracks/python/exercises/darts/approaches/if-statements [approach-struct-pattern-matching]: https://exercism.org/tracks/python/exercises/darts/approaches/struct-pattern-matching [approach-tuple-and-loop]: https://exercism.org/tracks/python/exercises/darts/approaches/tuple-and-loop From be7378d88bd07e511b8dfd32377c6d815eebb180 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 23 Jun 2025 15:33:52 -0700 Subject: [PATCH 793/826] Changed wording of task 4 and added notes about magic numbers in stub file and hint file. (#3930) --- .../concept/guidos-gorgeous-lasagna/.docs/hints.md | 4 +++- .../guidos-gorgeous-lasagna/.docs/instructions.md | 12 +++++++++--- exercises/concept/guidos-gorgeous-lasagna/lasagna.py | 6 +++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md index f2c07d50b6..96668ffbd0 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md @@ -24,7 +24,8 @@ - You need to define a [function][defining functions] with a single parameter representing the number of layers. - Use the [mathematical operator for multiplication][numbers] to multiply values. -- You could define an extra _constant_ for the time in minutes per layer rather than using a "magic number" in your code. +- You can define a PREPARATION_TIME _constant_ for the time in minutes per layer rather than using a ["magic + number"][magic-numbers] in your code. - This function should [return a value][return]. ## 4. Calculate total elapsed cooking time (prep + bake) in minutes @@ -43,6 +44,7 @@ [constants]: https://stackoverflow.com/a/2682752 [defining functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [docstrings]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings +[magic-numbers]: https://en.wikipedia.org/wiki/Magic_number_(programming) [naming]: https://realpython.com/python-variables/ [numbers]: https://docs.python.org/3/tutorial/introduction.html#numbers [pep257]: https://www.python.org/dev/peps/pep-0257/ diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md index fce16c4679..1991721c38 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md @@ -50,10 +50,16 @@ Assume each layer takes 2 minutes to prepare. ``` -## 4. Calculate total elapsed cooking time (prep + bake) in minutes +## 4. Calculate total elapsed time (prepping + baking) in minutes + +Define the `elapsed_time_in_minutes()` function that takes two parameters as arguments: + +- `number_of_layers` (_the number of layers added to the lasagna_) +- `elapsed_bake_time` (_the number of minutes the lasagna has spent baking in the oven already_). + +This function should return the total minutes you have been in the kitchen cooking — your preparation time layering + +the time the lasagna has spent baking in the oven. -Define the `elapsed_time_in_minutes()` function that takes two parameters as arguments: `number_of_layers` (_the number of layers added to the lasagna_) and `elapsed_bake_time` (_the number of minutes the lasagna has been baking in the oven_). -This function should return the total number of minutes you have been cooking, or the sum of your preparation time and the time the lasagna has already spent baking in the oven. ```python >>> def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py index a3df0ab2da..0e1a50d571 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py @@ -8,7 +8,7 @@ """ -#TODO: define the 'EXPECTED_BAKE_TIME' constant below. +#TODO: define your EXPECTED_BAKE_TIME (required) and PREPARATION_TIME (optional) constants below. #TODO: Remove 'pass' and complete the 'bake_time_remaining()' function below. @@ -27,9 +27,9 @@ def bake_time_remaining(): #TODO: Define the 'preparation_time_in_minutes()' function below. -# You might also consider defining a 'PREPARATION_TIME' constant. +# To avoid the use of magic numbers (see: https://en.wikipedia.org/wiki/Magic_number_(programming)), you should define a PREPARATION_TIME constant. # You can do that on the line below the 'EXPECTED_BAKE_TIME' constant. -# This will make it easier to do calculations. +# This will make it easier to do calculations, and make changes to your code. From cfbeb02f0c6cd398aa72beec6b55f9bab0c022ad Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 23 Jun 2025 16:20:24 -0700 Subject: [PATCH 794/826] Removed Scale generator from practices generators and added lsp and nth prime. (#3931) --- config.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/config.json b/config.json index 09bd642c98..f2d503c962 100644 --- a/config.json +++ b/config.json @@ -752,7 +752,7 @@ "slug": "nth-prime", "name": "Nth Prime", "uuid": "a20924d2-fe6d-4714-879f-3239feb9d2f2", - "practices": [], + "practices": ["generators"], "prerequisites": [ "basics", "bools", @@ -1485,7 +1485,7 @@ "slug": "scale-generator", "name": "Scale Generator", "uuid": "8cd58325-61fc-46fd-85f9-425b4c41f3de", - "practices": ["generators"], + "practices": [], "prerequisites": [ "basics", "bools", @@ -1503,12 +1503,7 @@ "slug": "largest-series-product", "name": "Largest Series Product", "uuid": "21624a3e-6e43-4c0e-94b0-dee5cdaaf2aa", - "practices": [ - "functions", - "higher-order-functions", - "functional-tools", - "anonymous-functions" - ], + "practices": ["generators"], "prerequisites": [ "basics", "conditionals", From 4758e9a05a5213021759383b9a9b68a8bf1c2806 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 23 Jun 2025 22:26:24 -0700 Subject: [PATCH 795/826] [Swift Scheduling]: Added Files for New Practice Exercise (#3876) * Added files for new practice execise swift-scheduling. * removed source url from meta config. * Added example solution and stub file. * Corrected stub file. * Added new leapyear test case and regenerated test cases and tests toml. * Updated config json to add exercise to track. * Rewrote example solution so CI would pass for python 3.7 - 3.9. --- config.json | 17 +++ .../swift-scheduling/.docs/instructions.md | 43 +++++++ .../swift-scheduling/.docs/introduction.md | 6 + .../swift-scheduling/.meta/config.json | 20 ++++ .../swift-scheduling/.meta/example.py | 57 +++++++++ .../swift-scheduling/.meta/template.j2 | 20 ++++ .../swift-scheduling/.meta/tests.toml | 51 ++++++++ .../swift-scheduling/swift_scheduling.py | 2 + .../swift-scheduling/swift_scheduling_test.py | 109 ++++++++++++++++++ 9 files changed, 325 insertions(+) create mode 100644 exercises/practice/swift-scheduling/.docs/instructions.md create mode 100644 exercises/practice/swift-scheduling/.docs/introduction.md create mode 100644 exercises/practice/swift-scheduling/.meta/config.json create mode 100644 exercises/practice/swift-scheduling/.meta/example.py create mode 100644 exercises/practice/swift-scheduling/.meta/template.j2 create mode 100644 exercises/practice/swift-scheduling/.meta/tests.toml create mode 100644 exercises/practice/swift-scheduling/swift_scheduling.py create mode 100644 exercises/practice/swift-scheduling/swift_scheduling_test.py diff --git a/config.json b/config.json index f2d503c962..9757366e05 100644 --- a/config.json +++ b/config.json @@ -1327,6 +1327,23 @@ ], "difficulty": 4 }, + { + "slug": "swift-scheduling", + "name": "Swift Scheduling", + "uuid": "ebddfc37-a3fc-4524-bd62-9c70f979713c", + "practices": [], + "prerequisites": ["basics", + "bools", + "conditionals", + "lists", + "list-methods", + "loops", + "numbers", + "strings", + "string-methods" + ], + "difficulty": 4 + }, { "slug": "minesweeper", "name": "Minesweeper", diff --git a/exercises/practice/swift-scheduling/.docs/instructions.md b/exercises/practice/swift-scheduling/.docs/instructions.md new file mode 100644 index 0000000000..77eeed4d6d --- /dev/null +++ b/exercises/practice/swift-scheduling/.docs/instructions.md @@ -0,0 +1,43 @@ +# Instructions + +Your task is to convert delivery date descriptions to _actual_ delivery dates, based on when the meeting started. + +There are two types of delivery date descriptions: + +1. Fixed: a predefined set of words. +2. Variable: words that have a variable component, but follow a predefined set of patterns. + +## Fixed delivery date descriptions + +There are three fixed delivery date descriptions: + +- `"NOW"` +- `"ASAP"` (As Soon As Possible) +- `"EOW"` (End Of Week) + +The following table shows how to translate them: + +| Description | Meeting start | Delivery date | +| ----------- | ----------------------------- | ----------------------------------- | +| `"NOW"` | - | Two hours after the meeting started | +| `"ASAP"` | Before 12:00 | Today at 17:00 | +| `"ASAP"` | After 12:00 | Tomorrow at 12:00 | +| `"EOW"` | Monday, Tuesday, or Wednesday | Friday at 17:00 | +| `"EOW"` | Thursday or Friday | Sunday at 20:00 | + +## Variable delivery date descriptions + +There are two variable delivery date description patterns: + +- `"M"` (N-th month) +- `"Q"` (N-th quarter) + +| Description | Meeting start | Delivery date | +| ----------- | -------------------------- | ----------------------------------------------------------- | +| `"M"` | Before N-th month | At 8:00 on the _first_ workday¹ of this year's N-th month | +| `"M"` | After or in N-th month | At 8:00 on the _first_ workday¹ of next year's N-th month | +| `"Q"` | Before or in N-th quarter² | At 8:00 on the _last_ workday¹ of this year's N-th quarter² | +| `"Q"` | After N-th quarter² | At 8:00 on the _last_ workday¹ of next year's N-th quarter² | + +¹ A workday is a Monday, Tuesday, Wednesday, Thursday, or Friday. +² A year has four quarters, each with three months: January/February/March, April/May/June, July/August/September, and October/November/December. diff --git a/exercises/practice/swift-scheduling/.docs/introduction.md b/exercises/practice/swift-scheduling/.docs/introduction.md new file mode 100644 index 0000000000..2322f813ff --- /dev/null +++ b/exercises/practice/swift-scheduling/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +This week, it is your turn to take notes in the department's planning meeting. +In this meeting, your boss will set delivery dates for all open work items. +Annoyingly, instead of specifying the _actual_ delivery dates, your boss will only _describe them_ in an abbreviated format. +As many of your colleagues won't be familiar with this corporate lingo, you'll need to convert these delivery date descriptions to actual delivery dates. diff --git a/exercises/practice/swift-scheduling/.meta/config.json b/exercises/practice/swift-scheduling/.meta/config.json new file mode 100644 index 0000000000..9d4c63bea1 --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "erikschierboom", + "bethanyg" + ], + "contributors": [], + "files": { + "solution": [ + "swift_scheduling.py" + ], + "test": [ + "swift_scheduling_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Convert delivery date descriptions to actual delivery dates.", + "source": "Original exercise idea from Eric Schierboom." +} diff --git a/exercises/practice/swift-scheduling/.meta/example.py b/exercises/practice/swift-scheduling/.meta/example.py new file mode 100644 index 0000000000..9f411819ab --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/example.py @@ -0,0 +1,57 @@ +from datetime import datetime, timedelta + + +def delivery_date(start, description): + start_date = datetime.fromisoformat(start) + + + if description == 'NOW': + due_date = start_date + timedelta(hours=2) + + if description == 'ASAP': + if str(start_date.time()) < '13:00:00': + due_date = start_date.replace(hour=17, minute=0) + else: + due_date = ( + start_date.replace(hour=13, minute=0) + + timedelta(days=1) + ) + + if description =='EOW': + if start_date.isoweekday() < 4: + due_date = ( + start_date.replace(hour=17, minute=0) + + timedelta(days=5 - start_date.isoweekday()) + ) + else: + due_date = ( + start_date.replace(hour=20, minute=0) + + timedelta(days=7 - start_date.isoweekday()) + ) + + if description.endswith('M'): + month = int(description[:-1]) + target = datetime(start_date.year, month, 1, 8, 0, 0) + + if start_date.month >= target.month: + target = target.replace(year=target.year + 1) + if target.isoweekday() not in (6,7) and target.day in range(1, 8): + due_date = target + else: + if target.isoweekday() == 6: due_date = target + timedelta(days = 2) + if target.isoweekday() == 7: due_date = target + timedelta(days = 1) + + if description.startswith('Q'): + target = int(description[1:]) + current = ((start_date.month + 2) // 3) + month = {"Q1":4,"Q2": 7,"Q3": 10,"Q4": 1}[description] + rollover = 1 if (current > target or target == 4) else 0 + + due_date = start_date.replace( + start_date.year + rollover, month, 1, 8, 0, 0 + ) - timedelta(days=1) + + if due_date.isoweekday() == 6: due_date -= timedelta(days=1) + if due_date.isoweekday() == 7: due_date -= timedelta(days=2) + + return due_date.isoformat() diff --git a/exercises/practice/swift-scheduling/.meta/template.j2 b/exercises/practice/swift-scheduling/.meta/template.j2 new file mode 100644 index 0000000000..eef5a58991 --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/template.j2 @@ -0,0 +1,20 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header(imports=imports, ignore=ignore) }} + + +{% macro test_case(case) -%} + {%- set input = case["input"] -%} + def test_{{ case["description"] | to_snake }}(self): + self.assertEqual( + {{ case["property"] | to_snake }}{{ case["input"]["meetingStart"], case["input"]["description"] }}, + "{{ case["expected"] }}" + ) +{%- endmacro %} + + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases %} + {{ test_case(case) }} + {% endfor %} diff --git a/exercises/practice/swift-scheduling/.meta/tests.toml b/exercises/practice/swift-scheduling/.meta/tests.toml new file mode 100644 index 0000000000..ef2aa5166b --- /dev/null +++ b/exercises/practice/swift-scheduling/.meta/tests.toml @@ -0,0 +1,51 @@ +# This is an auto-generated file. Regular comments will be removed when this +# file is regenerated. Regenerating will not touch any manually added keys, +# so comments can be added in a "comment" key. + +[1d0e6e72-f370-408c-bc64-5dafa9c6da73] +description = "NOW translates to two hours later" + +[93325e7b-677d-4d96-b017-2582af879dc2] +description = "ASAP before one in the afternoon translates to today at five in the afternoon" + +[cb4252a3-c4c1-41f6-8b8c-e7269733cef8] +description = "ASAP at one in the afternoon translates to tomorrow at one in the afternoon" + +[6fddc1ea-2fe9-4c60-81f7-9220d2f45537] +description = "ASAP after one in the afternoon translates to tomorrow at one in the afternoon" + +[25f46bf9-6d2a-4e95-8edd-f62dd6bc8a6e] +description = "EOW on Monday translates to Friday at five in the afternoon" + +[0b375df5-d198-489e-acee-fd538a768616] +description = "EOW on Tuesday translates to Friday at five in the afternoon" + +[4afbb881-0b5c-46be-94e1-992cdc2a8ca4] +description = "EOW on Wednesday translates to Friday at five in the afternoon" + +[e1341c2b-5e1b-4702-a95c-a01e8e96e510] +description = "EOW on Thursday translates to Sunday at eight in the evening" + +[bbffccf7-97f7-4244-888d-bdd64348fa2e] +description = "EOW on Friday translates to Sunday at eight in the evening" + +[d651fcf4-290e-407c-8107-36b9076f39b2] +description = "EOW translates to leap day" + +[439bf09f-3a0e-44e7-bad5-b7b6d0c4505a] +description = "2M before the second month of this year translates to the first workday of the second month of this year" + +[86d82e83-c481-4fb4-9264-625de7521340] +description = "11M in the eleventh month translates to the first workday of the eleventh month of next year" + +[0d0b8f6a-1915-46f5-a630-1ff06af9da08] +description = "4M in the ninth month translates to the first workday of the fourth month of next year" + +[06d401e3-8461-438f-afae-8d26aa0289e0] +description = "Q1 in the first quarter translates to the last workday of the first quarter of this year" + +[eebd5f32-b16d-4ecd-91a0-584b0364b7ed] +description = "Q4 in the second quarter translates to the last workday of the fourth quarter of this year" + +[c920886c-44ad-4d34-a156-dc4176186581] +description = "Q3 in the fourth quarter translates to the last workday of the third quarter of next year" diff --git a/exercises/practice/swift-scheduling/swift_scheduling.py b/exercises/practice/swift-scheduling/swift_scheduling.py new file mode 100644 index 0000000000..99fb9053eb --- /dev/null +++ b/exercises/practice/swift-scheduling/swift_scheduling.py @@ -0,0 +1,2 @@ +def delivery_date(start, description): + pass diff --git a/exercises/practice/swift-scheduling/swift_scheduling_test.py b/exercises/practice/swift-scheduling/swift_scheduling_test.py new file mode 100644 index 0000000000..3259d608bc --- /dev/null +++ b/exercises/practice/swift-scheduling/swift_scheduling_test.py @@ -0,0 +1,109 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/swift-scheduling/canonical-data.json +# File last updated on 2025-03-03 + +import unittest + +from swift_scheduling import ( + delivery_date, +) + + +class SwiftSchedulingTest(unittest.TestCase): + def test_now_translates_to_two_hours_later(self): + self.assertEqual( + delivery_date("2012-02-13T09:00:00", "NOW"), "2012-02-13T11:00:00" + ) + + def test_asap_before_one_in_the_afternoon_translates_to_today_at_five_in_the_afternoon( + self, + ): + self.assertEqual( + delivery_date("1999-06-03T09:45:00", "ASAP"), "1999-06-03T17:00:00" + ) + + def test_asap_at_one_in_the_afternoon_translates_to_tomorrow_at_one_in_the_afternoon( + self, + ): + self.assertEqual( + delivery_date("2008-12-21T13:00:00", "ASAP"), "2008-12-22T13:00:00" + ) + + def test_asap_after_one_in_the_afternoon_translates_to_tomorrow_at_one_in_the_afternoon( + self, + ): + self.assertEqual( + delivery_date("2008-12-21T14:50:00", "ASAP"), "2008-12-22T13:00:00" + ) + + def test_eow_on_monday_translates_to_friday_at_five_in_the_afternoon(self): + self.assertEqual( + delivery_date("2025-02-03T16:00:00", "EOW"), "2025-02-07T17:00:00" + ) + + def test_eow_on_tuesday_translates_to_friday_at_five_in_the_afternoon(self): + self.assertEqual( + delivery_date("1997-04-29T10:50:00", "EOW"), "1997-05-02T17:00:00" + ) + + def test_eow_on_wednesday_translates_to_friday_at_five_in_the_afternoon(self): + self.assertEqual( + delivery_date("2005-09-14T11:00:00", "EOW"), "2005-09-16T17:00:00" + ) + + def test_eow_on_thursday_translates_to_sunday_at_eight_in_the_evening(self): + self.assertEqual( + delivery_date("2011-05-19T08:30:00", "EOW"), "2011-05-22T20:00:00" + ) + + def test_eow_on_friday_translates_to_sunday_at_eight_in_the_evening(self): + self.assertEqual( + delivery_date("2022-08-05T14:00:00", "EOW"), "2022-08-07T20:00:00" + ) + + def test_eow_translates_to_leap_day(self): + self.assertEqual( + delivery_date("2008-02-25T10:30:00", "EOW"), "2008-02-29T17:00:00" + ) + + def test_2_m_before_the_second_month_of_this_year_translates_to_the_first_workday_of_the_second_month_of_this_year( + self, + ): + self.assertEqual( + delivery_date("2007-01-02T14:15:00", "2M"), "2007-02-01T08:00:00" + ) + + def test_11_m_in_the_eleventh_month_translates_to_the_first_workday_of_the_eleventh_month_of_next_year( + self, + ): + self.assertEqual( + delivery_date("2013-11-21T15:30:00", "11M"), "2014-11-03T08:00:00" + ) + + def test_4_m_in_the_ninth_month_translates_to_the_first_workday_of_the_fourth_month_of_next_year( + self, + ): + self.assertEqual( + delivery_date("2019-11-18T15:15:00", "4M"), "2020-04-01T08:00:00" + ) + + def test_q1_in_the_first_quarter_translates_to_the_last_workday_of_the_first_quarter_of_this_year( + self, + ): + self.assertEqual( + delivery_date("2003-01-01T10:45:00", "Q1"), "2003-03-31T08:00:00" + ) + + def test_q4_in_the_second_quarter_translates_to_the_last_workday_of_the_fourth_quarter_of_this_year( + self, + ): + self.assertEqual( + delivery_date("2001-04-09T09:00:00", "Q4"), "2001-12-31T08:00:00" + ) + + def test_q3_in_the_fourth_quarter_translates_to_the_last_workday_of_the_third_quarter_of_next_year( + self, + ): + self.assertEqual( + delivery_date("2022-10-06T11:00:00", "Q3"), "2023-09-29T08:00:00" + ) From b7a33e7bf71027d22dd35b57d6ec3ceb2b679b29 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 25 Jun 2025 10:46:23 -0700 Subject: [PATCH 796/826] Deprecated Minesweeper in favor of Flower Field. (#3933) Will be reimplemented with new UUID and slug. Code implementation not expected to change. --- config.json | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/config.json b/config.json index 9757366e05..6c55126c09 100644 --- a/config.json +++ b/config.json @@ -1344,21 +1344,6 @@ ], "difficulty": 4 }, - { - "slug": "minesweeper", - "name": "Minesweeper", - "uuid": "7e768b54-4591-4a30-9ddb-66ca13400ca3", - "practices": ["lists"], - "prerequisites": [ - "basics", - "bools", - "conditionals", - "lists", - "loops", - "numbers" - ], - "difficulty": 4 - }, { "slug": "spiral-matrix", "name": "Spiral Matrix", @@ -2235,6 +2220,15 @@ "prerequisites": [], "difficulty": 4, "status": "deprecated" + }, + { + "slug": "minesweeper", + "name": "Minesweeper", + "uuid": "7e768b54-4591-4a30-9ddb-66ca13400ca3", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "status": "deprecated" } ], "foregone": ["lens-person", "nucleotide-count", "parallel-letter-frequency"] From b3cf79fc0277db3988901b46059f50718ef8df28 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 25 Jun 2025 12:17:48 -0700 Subject: [PATCH 797/826] [Flower-Field]: Reimplement Minesweeper as Flower Field (#3934) * Reimplemented Minesweeper as Flower Field and regenerated test cases. * Removed accidental solution from stub file. --- config.json | 16 ++++ .../flower-field/.docs/instructions.append.md | 14 ++++ .../flower-field/.docs/instructions.md | 26 +++++++ .../flower-field/.docs/introduction.md | 7 ++ .../flower-field/.meta/additional_tests.json | 53 +++++++++++++ .../practice/flower-field/.meta/config.json | 22 ++++++ .../practice/flower-field/.meta/example.py | 40 ++++++++++ .../practice/flower-field/.meta/template.j2 | 27 +++++++ .../practice/flower-field/.meta/tests.toml | 46 +++++++++++ .../practice/flower-field/flower_field.py | 3 + .../flower-field/flower_field_test.py | 76 +++++++++++++++++++ 11 files changed, 330 insertions(+) create mode 100644 exercises/practice/flower-field/.docs/instructions.append.md create mode 100644 exercises/practice/flower-field/.docs/instructions.md create mode 100644 exercises/practice/flower-field/.docs/introduction.md create mode 100644 exercises/practice/flower-field/.meta/additional_tests.json create mode 100644 exercises/practice/flower-field/.meta/config.json create mode 100644 exercises/practice/flower-field/.meta/example.py create mode 100644 exercises/practice/flower-field/.meta/template.j2 create mode 100644 exercises/practice/flower-field/.meta/tests.toml create mode 100644 exercises/practice/flower-field/flower_field.py create mode 100644 exercises/practice/flower-field/flower_field_test.py diff --git a/config.json b/config.json index 6c55126c09..ff5fad2c81 100644 --- a/config.json +++ b/config.json @@ -1412,6 +1412,22 @@ ], "difficulty": 4 }, + { + "slug": "flower-field", + "name": "Flower Field", + "uuid": "0c2751c1-5d2f-499a-81b8-226e5092ea88", + "practices": ["lists"], + "prerequisites": [ + "conditionals", + "lists", + "list-methods", + "loops", + "numbers", + "strings", + "string-methods" + ], + "difficulty": 4 + }, { "slug": "rail-fence-cipher", "name": "Rail Fence Cipher", diff --git a/exercises/practice/flower-field/.docs/instructions.append.md b/exercises/practice/flower-field/.docs/instructions.append.md new file mode 100644 index 0000000000..2e20d97680 --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.append.md @@ -0,0 +1,14 @@ +# Instructions append + +## Exception messages + +Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. + +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` when the `board()` function receives malformed input. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a `ValueError` with a message, write the message as an argument to the `exception` type: + +```python +# when the board receives malformed input +raise ValueError("The board is invalid with current input.") +``` diff --git a/exercises/practice/flower-field/.docs/instructions.md b/exercises/practice/flower-field/.docs/instructions.md new file mode 100644 index 0000000000..bbdae0c2cb --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to add flower counts to empty squares in a completed Flower Field garden. +The garden itself is a rectangle board composed of squares that are either empty (`' '`) or a flower (`'*'`). + +For each empty square, count the number of flowers adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent flowers, leave it empty. +Otherwise replace it with the count of adjacent flowers. + +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): + +```text +·*·*· +··*·· +··*·· +····· +``` + +Which your code should transform into this: + +```text +1*3*1 +13*31 +·2*2· +·111· +``` diff --git a/exercises/practice/flower-field/.docs/introduction.md b/exercises/practice/flower-field/.docs/introduction.md new file mode 100644 index 0000000000..af9b615361 --- /dev/null +++ b/exercises/practice/flower-field/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Flower Field][history] is a compassionate reimagining of the popular game Minesweeper. +The object of the game is to find all the flowers in the garden using numeric hints that indicate how many flowers are directly adjacent (horizontally, vertically, diagonally) to a square. +"Flower Field" shipped in regional versions of Microsoft Windows in Italy, Germany, South Korea, Japan and Taiwan. + +[history]: https://web.archive.org/web/20020409051321fw_/http://rcm.usr.dsi.unimi.it/rcmweb/fnm/ diff --git a/exercises/practice/flower-field/.meta/additional_tests.json b/exercises/practice/flower-field/.meta/additional_tests.json new file mode 100644 index 0000000000..f55a3e2d48 --- /dev/null +++ b/exercises/practice/flower-field/.meta/additional_tests.json @@ -0,0 +1,53 @@ +{ + "exercise": "flower-field", + "version": "2.0", + "comments": [ + " The expected outputs are represented as arrays of strings to ", + " improve readability in this JSON file. ", + " Your track may choose whether to present the input as a single ", + " string (concatenating all the lines) or as the list. " + ], + "cases": [ + { + "description": "annotate 9", + "property": "annotate", + "input": { + "garden": [ + " ", + " * ", + " ", + " ", + " * " + ] + }, + "expected": [ + " 111", + " 1*1", + " 111", + "111 ", + "1*1 " + ] + }, + { + "description": "different len", + "property": "annotate", + "input": { + "garden": [ + " ", + "* ", + " " + ] + }, + "expected": {"error": "The board is invalid with current input."} + }, + { + "description": "invalid char", + "property": "annotate", + "input": { + "garden": ["X * "] + }, + "expected": {"error": "The board is invalid with current input."} + + } + ] +} diff --git a/exercises/practice/flower-field/.meta/config.json b/exercises/practice/flower-field/.meta/config.json new file mode 100644 index 0000000000..ef72b0341c --- /dev/null +++ b/exercises/practice/flower-field/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "habere-et-dispertire", + "bethanyg" + ], + "contributors": [ + "isaacg", + "kotp" + ], + "files": { + "solution": [ + "flower_field.py" + ], + "test": [ + "flower_field_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Mark all the flowers in a garden." +} diff --git a/exercises/practice/flower-field/.meta/example.py b/exercises/practice/flower-field/.meta/example.py new file mode 100644 index 0000000000..e3ae009cc2 --- /dev/null +++ b/exercises/practice/flower-field/.meta/example.py @@ -0,0 +1,40 @@ +def annotate(garden): + if not garden: + return [] + verify_board(garden) + row_len = len(garden[0]) + col_len = len(garden) + board = [list(row) for row in garden] + + for index1 in range(col_len): + for index2 in range(row_len): + if board[index1][index2] != ' ': + continue + + low = max(index2 - 1, 0) + high = min(index2 + 2, row_len + 2) + counts = garden[index1][low:high].count('*') + + if index1 > 0: + counts += garden[index1 - 1][low:high].count('*') + if index1 < col_len - 1: + counts += garden[index1 + 1][low:high].count('*') + if counts == 0: + continue + + board[index1][index2] = str(counts) + return [''.join(row) for row in board] + + +def verify_board(garden): + # Rows with different lengths + row_len = len(garden[0]) + if not all(len(row) == row_len for row in garden): + raise ValueError('The board is invalid with current input.') + + # Unknown character in board + character_set = set() + for row in garden: + character_set.update(row) + if character_set - set(' *'): + raise ValueError('The board is invalid with current input.') diff --git a/exercises/practice/flower-field/.meta/template.j2 b/exercises/practice/flower-field/.meta/template.j2 new file mode 100644 index 0000000000..f71afb3bcd --- /dev/null +++ b/exercises/practice/flower-field/.meta/template.j2 @@ -0,0 +1,27 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} + +{{ macros.header()}} + +{%- macro test_call(case) -%} + {{ case["property"] | to_snake }}({{ case["input"]["garden"] }}) +{%- endmacro %} + +class {{ exercise | camel_case }}Test(unittest.TestCase): + {% for case in cases -%} + def test_{{ case["description"] | to_snake }}(self): + self.assertEqual({{ test_call(case) }}, {{ case["expected"] }}) + {% endfor %} + + # Additional tests for this track + {% for case in additional_cases -%} + def test_{{ case["description"] | to_snake }}(self): + {%- if case is error_case %} + with self.assertRaises(ValueError) as err: + {{ test_call(case) }} + self.assertEqual(type(err.exception), ValueError) + self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}") + {%- else %} + self.assertEqual({{- test_call(case) }}, {{ case["expected"] }}) + {%- endif %} + {% endfor %} diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml new file mode 100644 index 0000000000..c2b24fdaf5 --- /dev/null +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -0,0 +1,46 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[237ff487-467a-47e1-9b01-8a891844f86c] +description = "no rows" + +[4b4134ec-e20f-439c-a295-664c38950ba1] +description = "no columns" + +[d774d054-bbad-4867-88ae-069cbd1c4f92] +description = "no flowers" + +[225176a0-725e-43cd-aa13-9dced501f16e] +description = "garden full of flowers" + +[3f345495-f1a5-4132-8411-74bd7ca08c49] +description = "flower surrounded by spaces" + +[6cb04070-4199-4ef7-a6fa-92f68c660fca] +description = "space surrounded by flowers" + +[272d2306-9f62-44fe-8ab5-6b0f43a26338] +description = "horizontal line" + +[c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e] +description = "horizontal line, flowers at edges" + +[a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5] +description = "vertical line" + +[b40f42f5-dec5-4abc-b167-3f08195189c1] +description = "vertical line, flowers at edges" + +[58674965-7b42-4818-b930-0215062d543c] +description = "cross" + +[dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] +description = "large garden" diff --git a/exercises/practice/flower-field/flower_field.py b/exercises/practice/flower-field/flower_field.py new file mode 100644 index 0000000000..88793e3779 --- /dev/null +++ b/exercises/practice/flower-field/flower_field.py @@ -0,0 +1,3 @@ +def annotate(garden): + # Function body starts here + pass diff --git a/exercises/practice/flower-field/flower_field_test.py b/exercises/practice/flower-field/flower_field_test.py new file mode 100644 index 0000000000..019f7357fd --- /dev/null +++ b/exercises/practice/flower-field/flower_field_test.py @@ -0,0 +1,76 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/flower-field/canonical-data.json +# File last updated on 2025-06-25 + +import unittest + +from flower_field import ( + annotate, +) + + +class FlowerFieldTest(unittest.TestCase): + def test_no_rows(self): + self.assertEqual(annotate([]), []) + + def test_no_columns(self): + self.assertEqual(annotate([""]), [""]) + + def test_no_flowers(self): + self.assertEqual(annotate([" ", " ", " "]), [" ", " ", " "]) + + def test_garden_full_of_flowers(self): + self.assertEqual(annotate(["***", "***", "***"]), ["***", "***", "***"]) + + def test_flower_surrounded_by_spaces(self): + self.assertEqual(annotate([" ", " * ", " "]), ["111", "1*1", "111"]) + + def test_space_surrounded_by_flowers(self): + self.assertEqual(annotate(["***", "* *", "***"]), ["***", "*8*", "***"]) + + def test_horizontal_line(self): + self.assertEqual(annotate([" * * "]), ["1*2*1"]) + + def test_horizontal_line_flowers_at_edges(self): + self.assertEqual(annotate(["* *"]), ["*1 1*"]) + + def test_vertical_line(self): + self.assertEqual(annotate([" ", "*", " ", "*", " "]), ["1", "*", "2", "*", "1"]) + + def test_vertical_line_flowers_at_edges(self): + self.assertEqual(annotate(["*", " ", " ", " ", "*"]), ["*", "1", " ", "1", "*"]) + + def test_cross(self): + self.assertEqual( + annotate([" * ", " * ", "*****", " * ", " * "]), + [" 2*2 ", "25*52", "*****", "25*52", " 2*2 "], + ) + + def test_large_garden(self): + self.assertEqual( + annotate([" * * ", " * ", " * ", " * *", " * * ", " "]), + ["1*22*1", "12*322", " 123*2", "112*4*", "1*22*2", "111111"], + ) + + # Additional tests for this track + def test_annotate_9(self): + self.assertEqual( + annotate([" ", " * ", " ", " ", " * "]), + [" 111", " 1*1", " 111", "111 ", "1*1 "], + ) + + def test_different_len(self): + with self.assertRaises(ValueError) as err: + annotate([" ", "* ", " "]) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual( + err.exception.args[0], "The board is invalid with current input." + ) + + def test_invalid_char(self): + with self.assertRaises(ValueError) as err: + annotate(["X * "]) + self.assertEqual(type(err.exception), ValueError) + self.assertEqual( + err.exception.args[0], "The board is invalid with current input." + ) From 3da0c164b661900647a208543a05a3b69f9a5e54 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 25 Jun 2025 16:50:44 -0700 Subject: [PATCH 798/826] Synced New exercise swift-schedling to problem specifications. (#3936) --- .../swift-scheduling/.docs/instructions.md | 29 ++++++++++++------- .../swift-scheduling/.meta/config.json | 3 +- .../swift-scheduling/swift_scheduling_test.py | 2 +- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/exercises/practice/swift-scheduling/.docs/instructions.md b/exercises/practice/swift-scheduling/.docs/instructions.md index 77eeed4d6d..6423a1066b 100644 --- a/exercises/practice/swift-scheduling/.docs/instructions.md +++ b/exercises/practice/swift-scheduling/.docs/instructions.md @@ -20,8 +20,8 @@ The following table shows how to translate them: | Description | Meeting start | Delivery date | | ----------- | ----------------------------- | ----------------------------------- | | `"NOW"` | - | Two hours after the meeting started | -| `"ASAP"` | Before 12:00 | Today at 17:00 | -| `"ASAP"` | After 12:00 | Tomorrow at 12:00 | +| `"ASAP"` | Before 13:00 | Today at 17:00 | +| `"ASAP"` | After or at 13:00 | Tomorrow at 13:00 | | `"EOW"` | Monday, Tuesday, or Wednesday | Friday at 17:00 | | `"EOW"` | Thursday or Friday | Sunday at 20:00 | @@ -32,12 +32,19 @@ There are two variable delivery date description patterns: - `"M"` (N-th month) - `"Q"` (N-th quarter) -| Description | Meeting start | Delivery date | -| ----------- | -------------------------- | ----------------------------------------------------------- | -| `"M"` | Before N-th month | At 8:00 on the _first_ workday¹ of this year's N-th month | -| `"M"` | After or in N-th month | At 8:00 on the _first_ workday¹ of next year's N-th month | -| `"Q"` | Before or in N-th quarter² | At 8:00 on the _last_ workday¹ of this year's N-th quarter² | -| `"Q"` | After N-th quarter² | At 8:00 on the _last_ workday¹ of next year's N-th quarter² | - -¹ A workday is a Monday, Tuesday, Wednesday, Thursday, or Friday. -² A year has four quarters, each with three months: January/February/March, April/May/June, July/August/September, and October/November/December. +| Description | Meeting start | Delivery date | +| ----------- | ------------------------- | --------------------------------------------------------- | +| `"M"` | Before N-th month | At 8:00 on the _first_ workday of this year's N-th month | +| `"M"` | After or in N-th month | At 8:00 on the _first_ workday of next year's N-th month | +| `"Q"` | Before or in N-th quarter | At 8:00 on the _last_ workday of this year's N-th quarter | +| `"Q"` | After N-th quarter | At 8:00 on the _last_ workday of next year's N-th quarter | + +~~~~exercism/note +A workday is a Monday, Tuesday, Wednesday, Thursday, or Friday. + +A year has four quarters, each with three months: +1. January/February/March +2. April/May/June +3. July/August/September +4. October/November/December. +~~~~ diff --git a/exercises/practice/swift-scheduling/.meta/config.json b/exercises/practice/swift-scheduling/.meta/config.json index 9d4c63bea1..ba6f97353f 100644 --- a/exercises/practice/swift-scheduling/.meta/config.json +++ b/exercises/practice/swift-scheduling/.meta/config.json @@ -16,5 +16,6 @@ ] }, "blurb": "Convert delivery date descriptions to actual delivery dates.", - "source": "Original exercise idea from Eric Schierboom." + "source": "Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/pull/2536" } diff --git a/exercises/practice/swift-scheduling/swift_scheduling_test.py b/exercises/practice/swift-scheduling/swift_scheduling_test.py index 3259d608bc..08ed2485b9 100644 --- a/exercises/practice/swift-scheduling/swift_scheduling_test.py +++ b/exercises/practice/swift-scheduling/swift_scheduling_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/swift-scheduling/canonical-data.json -# File last updated on 2025-03-03 +# File last updated on 2025-06-25 import unittest From 2ebcfe8656acad21011febdb1b94c0668563f7a6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 30 Jun 2025 07:39:15 -0700 Subject: [PATCH 799/826] [Mecha Munch Management] Fixed typos in Instructions, Introduction, & Stub File (#3937) * Fixed typos in instructions, introduction, and stub file. * Changed stub docstring to iterable from dict. --- .../.docs/instructions.md | 32 +++++++++++-------- .../.docs/introduction.md | 10 +++--- .../mecha-munch-management/dict_methods.py | 2 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index 7681547069..e679db7974 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -57,24 +57,28 @@ Create the function `update_recipes(, )` that takes an "i The function should return the new/updated "ideas" dictionary. ```python ->>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, -(('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)) +>>>update_recipes( + {'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, + (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),) + ) ... -{'Banana Bread' : {'Banana': 4, 'Apple': 1, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, - 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}} - ->>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}, - 'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}}, -[('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}), -('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}), -('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3})]) +{'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, + 'Raspberry Pie': {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}} + +>>> update_recipes( + {'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}, + 'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}}, + [('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}), + ('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}), + ('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3})] + ) ... -{'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}, +{'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, + 'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}, 'Pasta Primavera': {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}, 'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}} ``` diff --git a/exercises/concept/mecha-munch-management/.docs/introduction.md b/exercises/concept/mecha-munch-management/.docs/introduction.md index 17d6748771..b2938b8c21 100644 --- a/exercises/concept/mecha-munch-management/.docs/introduction.md +++ b/exercises/concept/mecha-munch-management/.docs/introduction.md @@ -87,7 +87,7 @@ This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last- ```python >>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'} -# Iterating in insertion order +# Iterating in insertion order (First in, first out) >>> for item in palette_II.items(): ... print(item) ... @@ -96,7 +96,7 @@ This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last- ('Purple baseline', '#161748') -# Iterating in the reverse direction. +# Iterating in the reverse direction. (Last in, first out) >>> for item in reversed(palette_II.items()): ... print (item) ... @@ -108,12 +108,12 @@ This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last- ## Sorting a Dictionary Dictionaries do not have a built-in sorting method. -However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `.items()`. +However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `dict.items()`. The sorted view can then be used to create a new dictionary. -Like iteration, the default sort is over dictionary `keys`. +Like iteration, the default sort is over the dictionary `keys`. ```python -# Default ordering for a dictionary is last in, first out (LIFO). +# Default ordering for a dictionary is insertion order (First in, first out). >>> color_palette = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', diff --git a/exercises/concept/mecha-munch-management/dict_methods.py b/exercises/concept/mecha-munch-management/dict_methods.py index f502fe00ab..92bfd7325f 100644 --- a/exercises/concept/mecha-munch-management/dict_methods.py +++ b/exercises/concept/mecha-munch-management/dict_methods.py @@ -26,7 +26,7 @@ def update_recipes(ideas, recipe_updates): """Update the recipe ideas dictionary. :param ideas: dict - The "recipe ideas" dict. - :param recipe_updates: dict - dictionary with updates for the ideas section. + :param recipe_updates: iterable - with updates for the ideas section. :return: dict - updated "recipe ideas" dict. """ From a06cf488b95f441dd91479eaae1c37796fdc8af8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 30 Jun 2025 08:40:39 -0700 Subject: [PATCH 800/826] Moved CI run to ubuntu-24. (#3938) --- .github/workflows/ci-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 8764211cbe..7bd6f0da19 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -12,7 +12,7 @@ on: jobs: housekeeping: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 From eedf04a724a32e4b344102c2279fa35d33d1d021 Mon Sep 17 00:00:00 2001 From: keiravillekode Date: Wed, 16 Jul 2025 11:25:42 +1000 Subject: [PATCH 801/826] 3 workflows use ubuntu-24.04 (#3942) --- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/pr-commenter.yml | 2 +- .github/workflows/stale.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 46319fa611..0ec90aee05 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -5,7 +5,7 @@ on: jobs: comment-on-new-issue: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: Comments for every NEW issue. steps: - name: Checkout diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index 3b2592cda1..f12714aec3 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -4,7 +4,7 @@ on: jobs: pr-comment: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: exercism/pr-commenter-action@085ef62d2a541a112c3ade1d24deea83665ea186 with: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 03023bc033..b10b6011d1 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,7 +6,7 @@ on: jobs: stale: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 with: From a51a10e66ca838419822411438513470aa5ac7b4 Mon Sep 17 00:00:00 2001 From: Juergen H <52623305+jmh-git@users.noreply.github.com> Date: Sun, 27 Jul 2025 21:48:23 +0200 Subject: [PATCH 802/826] Update instructions.append.md (#3947) Instruction does not match the unit test. The exception message tested in the unit test is more precise. Hence, I've changed the instruction. --- .../largest-series-product/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/largest-series-product/.docs/instructions.append.md b/exercises/practice/largest-series-product/.docs/instructions.append.md index 5a0f9b9206..b0aa9dce02 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.append.md +++ b/exercises/practice/largest-series-product/.docs/instructions.append.md @@ -10,7 +10,7 @@ To raise a `ValueError` with a message, write the message as an argument to the ```python # span of numbers is longer than number series -raise ValueError("span must be smaller than string length") +raise ValueError("span must not exceed string length") # span of number is negative raise ValueError("span must not be negative") From 0f0c01e0776b1ec94d3a96e7cace7f0e92ecf954 Mon Sep 17 00:00:00 2001 From: Sam Chukwuzube <68931805+princemuel@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:49:43 +0100 Subject: [PATCH 803/826] [Pangram]: fix redundant double iteration in dig deeper solution (#3948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: Current implementation creates an intermediate list from a set, which is redundant and inefficient. **Before:** ```python return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) == 26 ``` **After:** ```python return len(set(ltr for ltr in sentence.lower() if ltr.isalpha())) == 26 ``` Why this is better: - Eliminates double iteration: Old version iterates once for set(), again for list comprehension - Removes unnecessary list creation: No need to convert set → list just to count - Better memory usage: Generator expression feeds directly into set constructor - Same time complexity but more efficient constant factors Reviewed-by: bethanyg * docs(pangram): improve approach's content and links * docs(pangram): improve approach's content and links * docs(pangram): improve performance content and links * Added princemuel as contributor --------- Co-authored-by: BethanyG --- .../practice/pangram/.approaches/config.json | 6 ++++-- .../practice/pangram/.approaches/introduction.md | 4 +--- .../pangram/.approaches/set-len/content.md | 16 +++++++--------- .../pangram/.approaches/set-len/snippet.txt | 3 +-- exercises/practice/pangram/.articles/config.json | 3 ++- .../.articles/performance/code/Benchmark.py | 2 +- .../pangram/.articles/performance/content.md | 16 ++++++++-------- .../pangram/.articles/performance/snippet.md | 10 +++++----- 8 files changed, 29 insertions(+), 31 deletions(-) diff --git a/exercises/practice/pangram/.approaches/config.json b/exercises/practice/pangram/.approaches/config.json index 550a3b5e11..19a3b9f973 100644 --- a/exercises/practice/pangram/.approaches/config.json +++ b/exercises/practice/pangram/.approaches/config.json @@ -1,6 +1,7 @@ { "introduction": { - "authors": ["bobahop"] + "authors": ["bobahop"], + "contributors": ["princemuel"] }, "approaches": [ { @@ -22,7 +23,8 @@ "slug": "set-len", "title": "set with len", "blurb": "Use set with len.", - "authors": ["bobahop"] + "authors": ["bobahop"], + "contributors": ["princemuel"] }, { "uuid": "0a6d1bbf-6d60-4489-b8d9-b8375894628b", diff --git a/exercises/practice/pangram/.approaches/introduction.md b/exercises/practice/pangram/.approaches/introduction.md index cf5538c015..247348feae 100644 --- a/exercises/practice/pangram/.approaches/introduction.md +++ b/exercises/practice/pangram/.approaches/introduction.md @@ -42,9 +42,7 @@ For more information, check the [`set` with `issubset()` approach][approach-set- ```python def is_pangram(sentence): - return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) \ - == 26 - + return len(set(ltr for ltr in sentence.lower() if ltr.isalpha())) == 26 ``` For more information, check the [`set` with `len()` approach][approach-set-len]. diff --git a/exercises/practice/pangram/.approaches/set-len/content.md b/exercises/practice/pangram/.approaches/set-len/content.md index b647a01d49..6c0347d5c0 100644 --- a/exercises/practice/pangram/.approaches/set-len/content.md +++ b/exercises/practice/pangram/.approaches/set-len/content.md @@ -2,20 +2,18 @@ ```python def is_pangram(sentence): - return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) \ - == 26 - + return len(set(ltr for ltr in sentence.lower() if ltr.isalpha())) == 26 ``` - This approach first makes a [set][set] from the [`lower`][lower]cased characters of the `sentence`. -- The characters in the `set`are then iterated in a [list comprehension][list-comprehension]. -- The characters are filtered by an `if` [`isalpha()`][isalpha] statement, so that only alphabetic characters make it into the list. -- The function returns whether the [`len()`][len] of the [`list`][list] is `26`. -If the number of unique letters in the `set` is equal to the `26` letters in the alphabet, then the function will return `True`. +- The characters are filtered using a [set comprehension][set-comprehension] with an `if` [`isalpha()`][isalpha] statement, so that only alphabetic characters make it into the set. +- The function returns whether the [`len()`][len] of the [`set`][set] is `26`. + If the number of unique [ASCII][ascii] (American Standard Code for Information Interchange) letters in the `set` is equal to the `26` letters in the [ASCII][ascii] alphabet, then the function will return `True`. +- This approach is efficient because it uses a set to eliminate duplicates and directly checks the length, which is a constant time operation. [lower]: https://docs.python.org/3/library/stdtypes.html?#str.lower [set]: https://docs.python.org/3/library/stdtypes.html?#set -[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[set-comprehension]: https://realpython.com/python-set-comprehension/#introducing-set-comprehensions [isalpha]: https://docs.python.org/3/library/stdtypes.html?highlight=isalpha#str.isalpha [len]: https://docs.python.org/3/library/functions.html?#len -[list]: https://docs.python.org/3/library/stdtypes.html?#list +[ascii]: https://en.wikipedia.org/wiki/ASCII diff --git a/exercises/practice/pangram/.approaches/set-len/snippet.txt b/exercises/practice/pangram/.approaches/set-len/snippet.txt index 9a6a6d537b..16c2ce6806 100644 --- a/exercises/practice/pangram/.approaches/set-len/snippet.txt +++ b/exercises/practice/pangram/.approaches/set-len/snippet.txt @@ -1,3 +1,2 @@ def is_pangram(sentence): - return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) \ - == 26 + return len(set(ltr for ltr in sentence.lower() if ltr.isalpha())) == 26 diff --git a/exercises/practice/pangram/.articles/config.json b/exercises/practice/pangram/.articles/config.json index b7de79a678..ec053d3d0f 100644 --- a/exercises/practice/pangram/.articles/config.json +++ b/exercises/practice/pangram/.articles/config.json @@ -5,7 +5,8 @@ "slug": "performance", "title": "Performance deep dive", "blurb": "Deep dive to find out the most performant approach to determining a pangram.", - "authors": ["bobahop"] + "authors": ["bobahop"], + "contributors": ["princemuel"] } ] } diff --git a/exercises/practice/pangram/.articles/performance/code/Benchmark.py b/exercises/practice/pangram/.articles/performance/code/Benchmark.py index 1b42374447..6abefe1bee 100644 --- a/exercises/practice/pangram/.articles/performance/code/Benchmark.py +++ b/exercises/practice/pangram/.articles/performance/code/Benchmark.py @@ -28,7 +28,7 @@ def is_pangram(sentence): val = timeit.timeit("""is_pangram("Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.")""", """ def is_pangram(sentence): - return len([ltr for ltr in set(sentence.lower()) if ltr.isalpha()]) == 26 + return len(set(ltr for ltr in sentence.lower() if ltr.isalpha())) == 26 """, number=loops) / loops diff --git a/exercises/practice/pangram/.articles/performance/content.md b/exercises/practice/pangram/.articles/performance/content.md index c5546e948b..32f7fe24d5 100644 --- a/exercises/practice/pangram/.articles/performance/content.md +++ b/exercises/practice/pangram/.articles/performance/content.md @@ -15,18 +15,18 @@ For our performance investigation, we'll also include a fourth approach that [us To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. ``` -all: 1.505466179997893e-05 -all: 1.6063886400021147e-05 // with sentence.casefold() -set: 1.950172399985604e-06 -len: 3.7158977999933994e-06 -bit: 8.75982620002469e-06 +all: 1.8692991019000146e-05 +all: 1.686682232399926e-05 // with sentence.casefold() +set: 2.5181135439997888e-06 +len: 5.848111433000668e-06 +bit: 1.2118699087000096e-05 ``` - The `set` `len()` approach is not as fast as the `set` `issubset()` approach. -- The `all()` approach is slower than either `set` approach. -Using `casefold` was slower than using `lower`. +- The `all()` approach is significantly slower than either `set` approach (approximately 6-8x slower). + Using `casefold()` versus `lower()` showed variable performance, with each being faster in different runs. - Although the bit field approach may be faster in other languages, it is significantly slower in Python. -It is faster than the `all()` approach, but much slower than either `set` approach. + It is faster than the `all()` approach, but much slower than either `set` approach. [benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/pangram/.articles/performance/code/Benchmark.py [timeit]: https://docs.python.org/3/library/timeit.html diff --git a/exercises/practice/pangram/.articles/performance/snippet.md b/exercises/practice/pangram/.articles/performance/snippet.md index 0509fbee53..8542eba9fc 100644 --- a/exercises/practice/pangram/.articles/performance/snippet.md +++ b/exercises/practice/pangram/.articles/performance/snippet.md @@ -1,7 +1,7 @@ ``` -all: 1.505466179997893e-05 -all: 1.6063886400021147e-05 // with sentence.casefold() -set: 1.950172399985604e-06 -len: 3.7158977999933994e-06 -bit: 8.75982620002469e-06 +all: 1.8692991019000146e-05 +all: 1.686682232399926e-05 // with sentence.casefold() +set: 2.5181135439997888e-06 +len: 5.848111433000668e-06 +bit: 1.2118699087000096e-05 ``` From cda2f92fd5fe3a38542b82c8466929ab16307885 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 24 Aug 2025 06:16:32 -0700 Subject: [PATCH 804/826] De-indented error checking scenarios and added variables. (#3966) --- .../practice/linked-list/.meta/template.j2 | 16 ++++-- .../practice/linked-list/linked_list_test.py | 50 ++++++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2 index 604c5f5163..9568182196 100644 --- a/exercises/practice/linked-list/.meta/template.j2 +++ b/exercises/practice/linked-list/.meta/template.j2 @@ -39,13 +39,23 @@ {%- if error_operation == "pop" or error_operation == "shift" %} with self.assertRaises(IndexError) as err: lst.{{ error_operation }}() - self.assertEqual(type(err.exception), IndexError) + + to_validate = err.exception + to_validate_msg = err.exception.args[0] + + self.assertEqual(type(to_validate), IndexError) + self.assertEqual(to_validate_msg, "{{ error_msg }}") + {%- elif error_operation == "delete" %} with self.assertRaises(ValueError) as err: lst.{{ error_operation }}({{ value if value else 0 }}) - self.assertEqual(type(err.exception), ValueError) + + to_validate = err.exception + to_validate_msg = err.exception.args[0] + + self.assertEqual(type(to_validate), ValueError) + self.assertEqual(to_validate_msg, "{{ error_msg }}") {%- endif %} - self.assertEqual(err.exception.args[0], "{{ error_msg }}") {%- endif %} {%- endmacro %} diff --git a/exercises/practice/linked-list/linked_list_test.py b/exercises/practice/linked-list/linked_list_test.py index 6724a1ebce..c2c0d74e1a 100644 --- a/exercises/practice/linked-list/linked_list_test.py +++ b/exercises/practice/linked-list/linked_list_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/linked-list/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-08-24 import unittest @@ -168,8 +168,12 @@ def test_using_pop_raises_an_error_if_the_list_is_empty(self): lst = LinkedList() with self.assertRaises(IndexError) as err: lst.pop() - self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "List is empty") + + to_validate = err.exception + to_validate_msg = err.exception.args[0] + + self.assertEqual(type(to_validate), IndexError) + self.assertEqual(to_validate_msg, "List is empty") def test_can_return_with_pop_and_then_raise_an_error_if_empty(self): lst = LinkedList() @@ -179,15 +183,23 @@ def test_can_return_with_pop_and_then_raise_an_error_if_empty(self): self.assertEqual(lst.pop(), 5) with self.assertRaises(IndexError) as err: lst.pop() - self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "List is empty") + + to_validate = err.exception + to_validate_msg = err.exception.args[0] + + self.assertEqual(type(to_validate), IndexError) + self.assertEqual(to_validate_msg, "List is empty") def test_using_shift_raises_an_error_if_the_list_is_empty(self): lst = LinkedList() with self.assertRaises(IndexError) as err: lst.shift() - self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "List is empty") + + to_validate = err.exception + to_validate_msg = err.exception.args[0] + + self.assertEqual(type(to_validate), IndexError) + self.assertEqual(to_validate_msg, "List is empty") def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): lst = LinkedList() @@ -197,15 +209,23 @@ def test_can_return_with_shift_and_then_raise_an_error_if_empty(self): self.assertEqual(lst.shift(), 5) with self.assertRaises(IndexError) as err: lst.shift() - self.assertEqual(type(err.exception), IndexError) - self.assertEqual(err.exception.args[0], "List is empty") + + to_validate = err.exception + to_validate_msg = err.exception.args[0] + + self.assertEqual(type(to_validate), IndexError) + self.assertEqual(to_validate_msg, "List is empty") def test_using_delete_raises_an_error_if_the_list_is_empty(self): lst = LinkedList() with self.assertRaises(ValueError) as err: lst.delete(0) - self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Value not found") + + to_validate = err.exception + to_validate_msg = err.exception.args[0] + + self.assertEqual(type(to_validate), ValueError) + self.assertEqual(to_validate_msg, "Value not found") def test_using_delete_raises_an_error_if_the_value_is_not_found(self): lst = LinkedList() @@ -214,5 +234,9 @@ def test_using_delete_raises_an_error_if_the_value_is_not_found(self): self.assertEqual(lst.pop(), 7) with self.assertRaises(ValueError) as err: lst.delete(0) - self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Value not found") + + to_validate = err.exception + to_validate_msg = err.exception.args[0] + + self.assertEqual(type(to_validate), ValueError) + self.assertEqual(to_validate_msg, "Value not found") From aa0aa58c09584a1be80f6282bc7dd6f7f092aa26 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Aug 2025 22:37:27 -0700 Subject: [PATCH 805/826] August sync of docs to problem specs. (#3968) --- exercises/practice/luhn/.docs/instructions.md | 2 +- exercises/practice/say/.docs/instructions.md | 52 +++---------------- exercises/practice/say/.docs/introduction.md | 6 +++ .../practice/triangle/.docs/instructions.md | 5 ++ 4 files changed, 20 insertions(+), 45 deletions(-) create mode 100644 exercises/practice/say/.docs/introduction.md diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index df2e304a39..7702c6bbb5 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -41,7 +41,7 @@ If the sum is evenly divisible by 10, the original number is valid. ### Invalid Canadian SIN -The number to be checked is `066 123 468`. +The number to be checked is `066 123 478`. We start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index ad3d347782..3251c519ac 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -1,48 +1,12 @@ # Instructions -Given a number from 0 to 999,999,999,999, spell out that number in English. +Given a number, your task is to express it in English words exactly as your friend should say it out loud. +Yaʻqūb expects to use numbers from 0 up to 999,999,999,999. -## Step 1 +Examples: -Handle the basic case of 0 through 99. - -If the input to the program is `22`, then the output should be `'twenty-two'`. - -Your program should complain loudly if given a number outside the blessed range. - -Some good test cases for this program are: - -- 0 -- 14 -- 50 -- 98 -- -1 -- 100 - -### Extension - -If you're on a Mac, shell out to Mac OS X's `say` program to talk out loud. -If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`. - -## Step 2 - -Implement breaking a number up into chunks of thousands. - -So `1234567890` should yield a list like 1, 234, 567, and 890, while the far simpler `1000` should yield just 1 and 0. - -## Step 3 - -Now handle inserting the appropriate scale word between those chunks. - -So `1234567890` should yield `'1 billion 234 million 567 thousand 890'` - -The program must also report any values that are out of range. -It's fine to stop at "trillion". - -## Step 4 - -Put it all together to get nothing but plain English. - -`12345` should give `twelve thousand three hundred forty-five`. - -The program must also report any values that are out of range. +- 0 → zero +- 1 → one +- 12 → twelve +- 123 → one hundred twenty-three +- 1,234 → one thousand two hundred thirty-four diff --git a/exercises/practice/say/.docs/introduction.md b/exercises/practice/say/.docs/introduction.md new file mode 100644 index 0000000000..abd22851ef --- /dev/null +++ b/exercises/practice/say/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +Your friend Yaʻqūb works the counter at the busiest deli in town, slicing, weighing, and wrapping orders for a never-ending line of hungry customers. +To keep things moving, each customer takes a numbered ticket when they arrive. + +When it’s time to call the next person, Yaʻqūb reads their number out loud, always in full English words to make sure everyone hears it clearly. diff --git a/exercises/practice/triangle/.docs/instructions.md b/exercises/practice/triangle/.docs/instructions.md index ac39008726..755cb8d19d 100644 --- a/exercises/practice/triangle/.docs/instructions.md +++ b/exercises/practice/triangle/.docs/instructions.md @@ -13,6 +13,11 @@ A _scalene_ triangle has all sides of different lengths. For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. +~~~~exercism/note +We opted to not include tests for degenerate triangles (triangles that violate these rules) to keep things simpler. +You may handle those situations if you wish to do so, or safely ignore them. +~~~~ + In equations: Let `a`, `b`, and `c` be sides of the triangle. From 91eccfbf0a947d16f0ef74be181fe5095c143e73 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Aug 2025 22:59:35 -0700 Subject: [PATCH 806/826] [Variable Length Quantity]: Updated tests.toml & Regenerated Test Cases (#3969) * Updated tests.toml and regenerated test cases. * Regenerated tests under Python 3.11.5, since 3.13.2 was causing CI failure. --- .../variable-length-quantity/.meta/tests.toml | 80 ++++++++++++------- .../variable_length_quantity_test.py | 17 +++- 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/exercises/practice/variable-length-quantity/.meta/tests.toml b/exercises/practice/variable-length-quantity/.meta/tests.toml index 923fa0c1aa..53be789a38 100644 --- a/exercises/practice/variable-length-quantity/.meta/tests.toml +++ b/exercises/practice/variable-length-quantity/.meta/tests.toml @@ -1,81 +1,103 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [35c9db2e-f781-4c52-b73b-8e76427defd0] -description = "zero" +description = "Encode a series of integers, producing a series of bytes. -> zero" [be44d299-a151-4604-a10e-d4b867f41540] -description = "arbitrary single byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary single byte" + +[890bc344-cb80-45af-b316-6806a6971e81] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric single byte" [ea399615-d274-4af6-bbef-a1c23c9e1346] -description = "largest single byte" +description = "Encode a series of integers, producing a series of bytes. -> largest single byte" [77b07086-bd3f-4882-8476-8dcafee79b1c] -description = "smallest double byte" +description = "Encode a series of integers, producing a series of bytes. -> smallest double byte" [63955a49-2690-4e22-a556-0040648d6b2d] -description = "arbitrary double byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary double byte" + +[4977d113-251b-4d10-a3ad-2f5a7756bb58] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric double byte" [29da7031-0067-43d3-83a7-4f14b29ed97a] -description = "largest double byte" +description = "Encode a series of integers, producing a series of bytes. -> largest double byte" [3345d2e3-79a9-4999-869e-d4856e3a8e01] -description = "smallest triple byte" +description = "Encode a series of integers, producing a series of bytes. -> smallest triple byte" [5df0bc2d-2a57-4300-a653-a75ee4bd0bee] -description = "arbitrary triple byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary triple byte" + +[6731045f-1e00-4192-b5ae-98b22e17e9f7] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric triple byte" [f51d8539-312d-4db1-945c-250222c6aa22] -description = "largest triple byte" +description = "Encode a series of integers, producing a series of bytes. -> largest triple byte" [da78228b-544f-47b7-8bfe-d16b35bbe570] -description = "smallest quadruple byte" +description = "Encode a series of integers, producing a series of bytes. -> smallest quadruple byte" [11ed3469-a933-46f1-996f-2231e05d7bb6] -description = "arbitrary quadruple byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quadruple byte" + +[b45ef770-cbba-48c2-bd3c-c6362679516e] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric quadruple byte" [d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c] -description = "largest quadruple byte" +description = "Encode a series of integers, producing a series of bytes. -> largest quadruple byte" [91a18b33-24e7-4bfb-bbca-eca78ff4fc47] -description = "smallest quintuple byte" +description = "Encode a series of integers, producing a series of bytes. -> smallest quintuple byte" [5f34ff12-2952-4669-95fe-2d11b693d331] -description = "arbitrary quintuple byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quintuple byte" + +[9be46731-7cd5-415c-b960-48061cbc1154] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric quintuple byte" [7489694b-88c3-4078-9864-6fe802411009] -description = "maximum 32-bit integer input" +description = "Encode a series of integers, producing a series of bytes. -> maximum 32-bit integer input" [f9b91821-cada-4a73-9421-3c81d6ff3661] -description = "two single-byte values" +description = "Encode a series of integers, producing a series of bytes. -> two single-byte values" [68694449-25d2-4974-ba75-fa7bb36db212] -description = "two multi-byte values" +description = "Encode a series of integers, producing a series of bytes. -> two multi-byte values" [51a06b5c-de1b-4487-9a50-9db1b8930d85] -description = "many multi-byte values" +description = "Encode a series of integers, producing a series of bytes. -> many multi-byte values" [baa73993-4514-4915-bac0-f7f585e0e59a] -description = "one byte" +description = "Decode a series of bytes, producing a series of integers. -> one byte" [72e94369-29f9-46f2-8c95-6c5b7a595aee] -description = "two bytes" +description = "Decode a series of bytes, producing a series of integers. -> two bytes" [df5a44c4-56f7-464e-a997-1db5f63ce691] -description = "three bytes" +description = "Decode a series of bytes, producing a series of integers. -> three bytes" [1bb58684-f2dc-450a-8406-1f3452aa1947] -description = "four bytes" +description = "Decode a series of bytes, producing a series of integers. -> four bytes" [cecd5233-49f1-4dd1-a41a-9840a40f09cd] -description = "maximum 32-bit integer" +description = "Decode a series of bytes, producing a series of integers. -> maximum 32-bit integer" [e7d74ba3-8b8e-4bcb-858d-d08302e15695] -description = "incomplete sequence causes error" +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error" [aa378291-9043-4724-bc53-aca1b4a3fcb6] -description = "incomplete sequence causes error, even if value is zero" +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error, even if value is zero" [a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee] -description = "multiple values" +description = "Decode a series of bytes, producing a series of integers. -> multiple values" diff --git a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py index baeb236543..e059f82ee3 100644 --- a/exercises/practice/variable-length-quantity/variable_length_quantity_test.py +++ b/exercises/practice/variable-length-quantity/variable_length_quantity_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-08-28 import unittest @@ -17,6 +17,9 @@ def test_zero(self): def test_arbitrary_single_byte(self): self.assertEqual(encode([0x40]), [0x40]) + def test_asymmetric_single_byte(self): + self.assertEqual(encode([0x53]), [0x53]) + def test_largest_single_byte(self): self.assertEqual(encode([0x7F]), [0x7F]) @@ -26,6 +29,9 @@ def test_smallest_double_byte(self): def test_arbitrary_double_byte(self): self.assertEqual(encode([0x2000]), [0xC0, 0x0]) + def test_asymmetric_double_byte(self): + self.assertEqual(encode([0xAD]), [0x81, 0x2D]) + def test_largest_double_byte(self): self.assertEqual(encode([0x3FFF]), [0xFF, 0x7F]) @@ -35,6 +41,9 @@ def test_smallest_triple_byte(self): def test_arbitrary_triple_byte(self): self.assertEqual(encode([0x100000]), [0xC0, 0x80, 0x0]) + def test_asymmetric_triple_byte(self): + self.assertEqual(encode([0x1D59C]), [0x87, 0xAB, 0x1C]) + def test_largest_triple_byte(self): self.assertEqual(encode([0x1FFFFF]), [0xFF, 0xFF, 0x7F]) @@ -44,6 +53,9 @@ def test_smallest_quadruple_byte(self): def test_arbitrary_quadruple_byte(self): self.assertEqual(encode([0x8000000]), [0xC0, 0x80, 0x80, 0x0]) + def test_asymmetric_quadruple_byte(self): + self.assertEqual(encode([0x357704]), [0x81, 0xD5, 0xEE, 0x4]) + def test_largest_quadruple_byte(self): self.assertEqual(encode([0xFFFFFFF]), [0xFF, 0xFF, 0xFF, 0x7F]) @@ -53,6 +65,9 @@ def test_smallest_quintuple_byte(self): def test_arbitrary_quintuple_byte(self): self.assertEqual(encode([0xFF000000]), [0x8F, 0xF8, 0x80, 0x80, 0x0]) + def test_asymmetric_quintuple_byte(self): + self.assertEqual(encode([0x86656105]), [0x88, 0xB3, 0x95, 0xC2, 0x5]) + def test_maximum_32_bit_integer_input(self): self.assertEqual(encode([0xFFFFFFFF]), [0x8F, 0xFF, 0xFF, 0xFF, 0x7F]) From 3297093c98982acebf21549b3f3d4b1be4add029 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 00:16:42 -0700 Subject: [PATCH 807/826] Bump actions/checkout from 4.2.2 to 5.0.0 (#3975) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 5.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/11bd71901bbe5b1630ceea73d27597364c9af683...08c6903cd8c0fde910a37f88322edcfb5dd907a8) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 7bd6f0da19..1279566e79 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 0ec90aee05..5472e7d95e 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 88c348a366..428be225ca 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - name: Run test-runner run: docker compose run test-runner From 64ce10de654150a21ff13b1ee94984a0284e44de Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 22 Sep 2025 14:16:45 -0700 Subject: [PATCH 808/826] [Cater Waiter & Sets Concept]: Better Differentiation of Emoji Examples in Introduction & About Files (#3991) * Changed emoji examples in indtroduction to be more differentiated. * Differentiated emoji examples in about.md doc as well. * Change one more example to differentiate. --- concepts/sets/about.md | 32 ++++++++++--------- .../cater-waiter/.docs/introduction.md | 20 ++++++------ 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 058be5c7de..204df38057 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -34,12 +34,13 @@ While sets can be created in many different ways, the most straightforward const A `set` can be directly entered as a _set literal_ with curly `{}` brackets and commas between elements. Duplicates are silently omitted: + ```python ->>> one_element = {'😀'} -{'😀'} +>>> one_element = {'➕'} +{'➕'} ->>> multiple_elements = {'😀', '😃', '😄', '😁'} -{'😀', '😃', '😄', '😁'} +>>> multiple_elements = {'➕', '🔻', '🔹', '🔆'} +{'➕', '🔻', '🔹', '🔆'} >>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!', '¡Hola!','Привіт!', 'こんにちは!', @@ -108,9 +109,9 @@ Remember: sets can hold different datatypes and _nested_ datatypes, but all `set ```python # Attempting to use a list for a set member throws a TypeError ->>> lists_as_elements = {['😅','🤣'], - ['😂','🙂','🙃'], - ['😜', '🤪', '😝']} +>>> lists_as_elements = {['🌈','💦'], + ['☁️','⭐️','🌍'], + ['⛵️', '🚲', '🚀']} Traceback (most recent call last): File "", line 1, in @@ -118,9 +119,9 @@ TypeError: unhashable type: 'list' # Standard sets are mutable, so they cannot be hashed. ->>> sets_as_elements = {{'😅','🤣'}, - {'😂','🙂','🙃'}, - {'😜', '🤪', '😝'}} +>>> sets_as_elements = {{'🌈','💦'}, + {'☁️','⭐️','🌍'}, + {'⛵️', '🚲', '🚀'}} Traceback (most recent call last): File "", line 1, in @@ -131,14 +132,15 @@ However, a `set` of `sets` can be created via type `frozenset()`: ```python # Frozensets don't have a literal form. ->>> set_1 = frozenset({'😜', '😝', '🤪'}) ->>> set_2 = frozenset({'😅', '🤣'}) ->>> set_3 = frozenset({'😂', '🙂', '🙃'}) +>>> set_1 = frozenset({'🌈','💦'}) +>>> set_2 = frozenset({'☁️','⭐️','🌍'}) +>>> set_3 = frozenset({'⛵️', '🚲', '🚀'}) >>> frozen_sets_as_elements = {set_1, set_2, set_3} >>> frozen_sets_as_elements -{frozenset({'😜', '😝', '🤪'}), frozenset({'😅', '🤣'}), -frozenset({'😂', '🙂', '🙃'})} +{frozenset({'⛵️', '🚀', '🚲'}), + frozenset({'🌈', '💦'}), + frozenset({'☁️', '⭐️', '🌍'})} ``` diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 926aea3d90..0993c4f0aa 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -32,11 +32,11 @@ A `set` can be directly entered as a _set literal_ with curly `{}` brackets and Duplicates are silently omitted: ```python ->>> one_element = {'😀'} -{'😀'} +>>> one_element = {'➕'} +{'➕'} ->>> multiple_elements = {'😀', '😃', '😄', '😁'} -{'😀', '😃', '😄', '😁'} +>>> multiple_elements = {'➕', '🔻', '🔹', '🔆'} +{'➕', '🔻', '🔹', '🔆'} >>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!', '¡Hola!','Привіт!', 'こんにちは!', @@ -91,9 +91,9 @@ Sets can hold different datatypes and _nested_ datatypes, but all `set` elements ```python # Attempting to use a list for a set member throws a TypeError ->>> lists_as_elements = {['😅','🤣'], - ['😂','🙂','🙃'], - ['😜', '🤪', '😝']} +>>> lists_as_elements = {['🌈','💦'], + ['☁️','⭐️','🌍'], + ['⛵️', '🚲', '🚀']} Traceback (most recent call last): File "", line 1, in @@ -101,9 +101,9 @@ TypeError: unhashable type: 'list' # Standard sets are mutable, so they cannot be hashed. ->>> sets_as_elements = {{'😅','🤣'}, - {'😂','🙂','🙃'}, - {'😜', '🤪', '😝'}} +>>> sets_as_elements = {{'🌈','💦'}, + {'☁️','⭐️','🌍'}, + {'⛵️', '🚲', '🚀'}} Traceback (most recent call last): File "", line 1, in From 67d865b48e310810f0ca8e282ae7f6269d0b088b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 22 Sep 2025 14:44:15 -0700 Subject: [PATCH 809/826] Fixed grammatical error in paragraph describing * with zip(). (#3992) --- concepts/unpacking-and-multiple-assignment/about.md | 5 +++-- exercises/concept/locomotive-engineer/.docs/introduction.md | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index a24e2c6d1a..d4b9168ad1 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -364,8 +364,8 @@ numbers = [1, 2, 3] 1 ``` -Using `*` unpacking with the `zip()` function is another common use case. -Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped: +Using `*` unpacking with the [`zip()` built-in][zip] is another common use case. +The `zip()` function takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped: ```python >>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True]) @@ -383,3 +383,4 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp [view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views +[zip]: https://docs.python.org/3/library/functions.html#zip diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 39ba5b4909..b10fff1217 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -351,8 +351,8 @@ numbers = [1, 2, 3] 1 ``` -Using `*` unpacking with the `zip()` function is another common use case. -Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped: +Using `*` unpacking with the [`zip()` built-in][zip] is another common use case. +The `zip()` function takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped: ```python >>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True]) @@ -369,3 +369,4 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp [view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views +[zip]: https://docs.python.org/3/library/functions.html#zip From ab7852e88ed0ba9cab5b1123b6d66132fa6ee674 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 22 Sep 2025 15:20:20 -0700 Subject: [PATCH 810/826] [Meltdown Mitigation]: Corrected "critical" to "balanced in criticality" on line 15 of Instructions (#3993) * Corrected critical to balanced in criticaltiy on line 15 of intructions. * Corrected docstring to refer to balanced in criticality. --- exercises/concept/meltdown-mitigation/.docs/instructions.md | 4 ++-- exercises/concept/meltdown-mitigation/conditionals.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/meltdown-mitigation/.docs/instructions.md b/exercises/concept/meltdown-mitigation/.docs/instructions.md index 3d6d96d0cd..cd8995de8a 100644 --- a/exercises/concept/meltdown-mitigation/.docs/instructions.md +++ b/exercises/concept/meltdown-mitigation/.docs/instructions.md @@ -11,8 +11,8 @@ The following three tasks are all related to writing code for maintaining ideal ## 1. Check for criticality -The first thing a control system has to do is check if the reactor is balanced in criticality. -A reactor is said to be critical if it satisfies the following conditions: +The first thing a control system has to do is check if the reactor is _balanced in criticality_. +A reactor is said to be balanced in criticality if it satisfies the following conditions: - The temperature is less than 800 K. - The number of neutrons emitted per second is greater than 500. diff --git a/exercises/concept/meltdown-mitigation/conditionals.py b/exercises/concept/meltdown-mitigation/conditionals.py index 1eb0a571ff..ff5769d835 100644 --- a/exercises/concept/meltdown-mitigation/conditionals.py +++ b/exercises/concept/meltdown-mitigation/conditionals.py @@ -8,7 +8,7 @@ def is_criticality_balanced(temperature, neutrons_emitted): :param neutrons_emitted: int or float - number of neutrons emitted per second. :return: bool - is criticality balanced? - A reactor is said to be critical if it satisfies the following conditions: + A reactor is said to be balanced in criticality if it satisfies the following conditions: - The temperature is less than 800 K. - The number of neutrons emitted per second is greater than 500. - The product of temperature and neutrons emitted per second is less than 500000. From fbc67692497ab85f0b7678cb7cba35c69d026e86 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 22 Sep 2025 17:32:05 -0700 Subject: [PATCH 811/826] September exercise sync of two-bucket tests. (#3994) New tests were added and solutions need to be re-run. --- exercises/practice/two-bucket/.meta/tests.toml | 6 ++++++ exercises/practice/two-bucket/two_bucket_test.py | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/exercises/practice/two-bucket/.meta/tests.toml b/exercises/practice/two-bucket/.meta/tests.toml index d6ff02f53e..a3fe533ece 100644 --- a/exercises/practice/two-bucket/.meta/tests.toml +++ b/exercises/practice/two-bucket/.meta/tests.toml @@ -27,6 +27,12 @@ description = "Measure one step using bucket one of size 1 and bucket two of siz [eb329c63-5540-4735-b30b-97f7f4df0f84] description = "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two" +[58d70152-bf2b-46bb-ad54-be58ebe94c03] +description = "Measure using bucket one much bigger than bucket two" + +[9dbe6499-caa5-4a58-b5ce-c988d71b8981] +description = "Measure using bucket one much smaller than bucket two" + [449be72d-b10a-4f4b-a959-ca741e333b72] description = "Not possible to reach the goal" diff --git a/exercises/practice/two-bucket/two_bucket_test.py b/exercises/practice/two-bucket/two_bucket_test.py index b7d1cc0195..d097866e5b 100644 --- a/exercises/practice/two-bucket/two_bucket_test.py +++ b/exercises/practice/two-bucket/two_bucket_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-bucket/canonical-data.json -# File last updated on 2023-07-21 +# File last updated on 2025-09-23 import unittest @@ -40,6 +40,12 @@ def test_measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_ ): self.assertEqual(measure(2, 3, 3, "one"), (2, "two", 2)) + def test_measure_using_bucket_one_much_bigger_than_bucket_two(self): + self.assertEqual(measure(5, 1, 2, "one"), (6, "one", 1)) + + def test_measure_using_bucket_one_much_smaller_than_bucket_two(self): + self.assertEqual(measure(3, 15, 9, "one"), (6, "two", 0)) + def test_not_possible_to_reach_the_goal(self): with self.assertRaisesWithMessage(ValueError): measure(6, 15, 5, "one") From cb300edc5c48ee76bac397293fa7aa0311505c88 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 26 Sep 2025 12:36:46 -0700 Subject: [PATCH 812/826] [Little Sister's Vocab]: Fixed up Code Examples for `str.join()` & Added an Additional Hint. (#3995) * Fixed up code examples for join and added an additional hint. * Touched up hints phrasing, added no loop directive to instructions, and added additional examples to concept about. * Typo correction. * Corrected separator misspelling. * Cleaned up in-line comments per PR review. * Fixed capitalization on inline comments in last join example. --- concepts/strings/about.md | 38 +++++++++++++++++-- .../little-sisters-vocab/.docs/hints.md | 7 +++- .../.docs/instructions.md | 3 ++ .../.docs/introduction.md | 32 ++++++++++++++-- 4 files changed, 71 insertions(+), 9 deletions(-) diff --git a/concepts/strings/about.md b/concepts/strings/about.md index 0107f6e70f..064c4c11bc 100644 --- a/concepts/strings/about.md +++ b/concepts/strings/about.md @@ -9,7 +9,7 @@ The Python docs also provide a very detailed [unicode HOWTO][unicode how-to] tha Strings implement all [common sequence operations][common sequence operations] and can be iterated through using `for item in ` or `for index, item in enumerate()` syntax. Individual code points (_strings of length 1_) can be referenced by `0-based index` number from the left, or `-1-based index` number from the right. -Strings can be concatenated with `+`, or via `.join()`, split via `.split()`, and offer multiple formatting and assembly options. +Strings can be concatenated with `+`, or via `.join()`, split via `.split()`, and offer multiple formatting, assembly, and templating options. A `str` literal can be declared via single `'` or double `"` quotes. The escape `\` character is available as needed. @@ -168,12 +168,12 @@ sentence = word + " " + "means" + " " + number + " in " + language + "." "дев'ять means nine in Ukrainian." ``` -If a `list`, `tuple`, `set` or other collection of individual strings needs to be combined into a single `str`, [`.join()`][str-join], is a better option: +If a `list`, `tuple`, `set` or other collection of individual strings needs to be combined into a single `str`, [`.join()`][str-join] is a better option: ```python # str.join() makes a new string from the iterables elements. ->>> chickens = ["hen", "egg", "rooster"] +>>> chickens = ["hen", "egg", "rooster"] # Lists are iterable. >>> ' '.join(chickens) 'hen egg rooster' @@ -183,6 +183,34 @@ If a `list`, `tuple`, `set` or other collection of individual strings needs to b >>> ' 🌿 '.join(chickens) 'hen 🌿 egg 🌿 rooster' + + +# Any iterable can be used as input. +>>> flowers = ("rose", "daisy", "carnation") # Tuples are iterable. +>>> '*-*'.join(flowers) +'rose*-*daisy*-*carnation' + +>>> flowers = {"rose", "daisy", "carnation"} # Sets are iterable, but output order is not guaranteed. +>>> '*-*'.join(flowers) +'rose*-*carnation*-*daisy' + +>>> phrase = "This is my string" # Strings are iterable, but be careful! +>>> '..'.join(phrase) +'T..h..i..s.. ..i..s.. ..m..y.. ..s..t..r..i..n..g' + + +# Separators are inserted **between** elements, but can be any string (including spaces). +# This can be exploited for interesting effects. +>>> under_words = ['under', 'current', 'sea', 'pin', 'dog', 'lay'] +>>> separator = ' ⤴️ under' # Note the leading space, but no trailing space. +>>> separator.join(under_words) +'under ⤴️ undercurrent ⤴️ undersea ⤴️ underpin ⤴️ underdog ⤴️ underlay' + +# The separator can be composed different ways, as long as the result is a string. +>>> upper_words = ['upper', 'crust', 'case', 'classmen', 'most', 'cut'] +>>> separator = ' 🌟 ' + upper_words[0] # This becomes one string, similar to ' ⤴️ under'. +>>> separator.join(upper_words) + 'upper 🌟 uppercrust 🌟 uppercase 🌟 upperclassmen 🌟 uppermost 🌟 uppercut' ``` Strings support all [common sequence operations][common sequence operations]. @@ -194,7 +222,9 @@ Indexes _with_ items can be iterated through in a loop via `for index, item in e >>> exercise = 'လေ့ကျင့်' -# Note that there are more code points than perceived glyphs or characters +# Note that there are more code points than perceived glyphs or characters. +# Care should be used when iterating over languages that use +# combining characters, or when dealing with emoji. >>> for code_point in exercise: ... print(code_point) ... diff --git a/exercises/concept/little-sisters-vocab/.docs/hints.md b/exercises/concept/little-sisters-vocab/.docs/hints.md index 2e5540805c..0be143a7f6 100644 --- a/exercises/concept/little-sisters-vocab/.docs/hints.md +++ b/exercises/concept/little-sisters-vocab/.docs/hints.md @@ -14,8 +14,11 @@ There's four activities in the assignment, each with a set of text or words to w ## 2. Add prefixes to word groups -- Believe it or not, [`str.join()`][str-join] is all you need here. -- Like [`str.split()`][str-split]`, `str.join()` can take an arbitrary-length string, made up of any unicode code points. +- Believe it or not, [`str.join()`][str-join] is all you need here. **A loop is not required**. +- The tests will be feeding your function a `list`. There will be no need to alter this `list` if you can figure out a good delimiter string. +- Remember that delimiter strings go between elements and "glue" them together into a single string. Delimiters are inserted _without_ space, although you can include space characters within them. +- Like [`str.split()`][str-split], `str.join()` can process an arbitrary-length string, made up of any unicode code points. _Unlike_ `str.split()`, it can also process arbitrary-length iterables like `list`, `tuple`, and `set`. + ## 3. Remove a suffix from a word diff --git a/exercises/concept/little-sisters-vocab/.docs/instructions.md b/exercises/concept/little-sisters-vocab/.docs/instructions.md index 2658bb980a..991845a704 100644 --- a/exercises/concept/little-sisters-vocab/.docs/instructions.md +++ b/exercises/concept/little-sisters-vocab/.docs/instructions.md @@ -40,6 +40,9 @@ Implement the `make_word_groups()` function that takes a `vocab_wor `[, , .... ]`, and returns a string with the prefix applied to each word that looks like: `' :: :: :: '`. +Creating a `for` or `while` loop to process the input is not needed here. +Think carefully about which string methods (and delimiters) you could use instead. + ```python >>> make_word_groups(['en', 'close', 'joy', 'lighten']) diff --git a/exercises/concept/little-sisters-vocab/.docs/introduction.md b/exercises/concept/little-sisters-vocab/.docs/introduction.md index 7aaea474ee..3b7ee76b27 100644 --- a/exercises/concept/little-sisters-vocab/.docs/introduction.md +++ b/exercises/concept/little-sisters-vocab/.docs/introduction.md @@ -50,7 +50,7 @@ If a `list`, `tuple`, `set` or other collection of individual strings needs to b ```python # str.join() makes a new string from the iterables elements. ->>> chickens = ["hen", "egg", "rooster"] +>>> chickens = ["hen", "egg", "rooster"] # Lists are iterable. >>> ' '.join(chickens) 'hen egg rooster' @@ -60,6 +60,34 @@ If a `list`, `tuple`, `set` or other collection of individual strings needs to b >>> ' 🌿 '.join(chickens) 'hen 🌿 egg 🌿 rooster' + + +# Any iterable can be used as input. +>>> flowers = ("rose", "daisy", "carnation") # Tuples are iterable. +>>> '*-*'.join(flowers) +'rose*-*daisy*-*carnation' + +>>> flowers = {"rose", "daisy", "carnation"} # Sets are iterable, but output order is not guaranteed. +>>> '*-*'.join(flowers) +'rose*-*carnation*-*daisy' + +>>> phrase = "This is my string" # Strings are iterable, but be careful! +>>> '..'.join(phrase) +'T..h..i..s.. ..i..s.. ..m..y.. ..s..t..r..i..n..g' + + +# Separators are inserted **between** elements, but can be any string (including spaces). +# This can be exploited for interesting effects. +>>> under_words = ['under', 'current', 'sea', 'pin', 'dog', 'lay'] +>>> separator = ' ⤴️ under' +>>> separator.join(under_words) +'under ⤴️ undercurrent ⤴️ undersea ⤴️ underpin ⤴️ underdog ⤴️ underlay' + +# The separator can be composed different ways, as long as the result is a string. +>>> upper_words = ['upper', 'crust', 'case', 'classmen', 'most', 'cut'] +>>> separator = ' 🌟 ' + upper_words[0] +>>> separator.join(upper_words) + 'upper 🌟 uppercrust 🌟 uppercase 🌟 upperclassmen 🌟 uppermost 🌟 uppercut' ``` Code points within a `str` can be referenced by `0-based index` number from the left: @@ -95,7 +123,6 @@ creative = '창의적인' ``` - There is no separate “character” or "rune" type in Python, so indexing a string produces a new `str` of length 1: @@ -169,7 +196,6 @@ Strings can also be broken into smaller strings via [`.split()`] ['feline', 'four-footed', 'ferocious', 'furry'] ``` - Separators for `.split()` can be more than one character. The **whole string** is used for split matching. From 8c285ac4c2ac068d20670463eb344a5f1b6ac730 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:17:34 -0700 Subject: [PATCH 813/826] Bump actions/setup-python from 5.6.0 to 6.0.0 (#4000) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.0.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/a26af69be951a213d495a4c3e4e4022e16d87065...e797f83bcb11b83ae66e0230d6156d7c80228e7c) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 1279566e79..3a80387e3a 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c with: python-version: ${{ matrix.python-version }} From 61dbb3a46b802b2263c4f6c41a28a5706b85eaaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 11:17:56 -0700 Subject: [PATCH 814/826] Bump actions/stale from 9.1.0 to 10.0.0 (#3999) Bumps [actions/stale](https://github.com/actions/stale) from 9.1.0 to 10.0.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/5bef64f19d7facfb25b37b414482c7164d639639...3a9db7e6a41a89f618792c92c0e97cc736e1b13f) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b10b6011d1..1c4ddca6a9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-24.04 steps: - - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 + - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 From b3a9d9a35fc96802e8b5c04948a696aad59a1d6e Mon Sep 17 00:00:00 2001 From: Mark Rosemaker <48681726+MarkRosemaker@users.noreply.github.com> Date: Sun, 12 Oct 2025 18:56:04 +0200 Subject: [PATCH 815/826] [Tree Building Exercise & Class Inheritance Concept] Spelling Fixes (#4009) * Fix possessive 'its' spelling in documentation and tests * Corrected 'possibly' to 'possible * Revert "Corrected 'possibly' to 'possible" This reverts commit 9a42041c455913d8c97f98f8e45eca3cd64219e3. * revert: changes in reference/ folder [no important files changed] --- concepts/class-inheritance/about.md | 2 +- exercises/practice/tree-building/.meta/example.py | 2 +- exercises/practice/tree-building/tree_building_test.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/concepts/class-inheritance/about.md b/concepts/class-inheritance/about.md index 5db7909e2c..9f1bdf30cd 100644 --- a/concepts/class-inheritance/about.md +++ b/concepts/class-inheritance/about.md @@ -7,7 +7,7 @@ In situations where only a small amount of functionality needs to be customized `Inheritance` describes `is a kind of` relationship between two or more classes, abstracting common details into super (_base_ or _parent_) class and storing specific ones in the subclass (_derived class_ or _child class_). -To create a child class, specify the parent class name inside the pair of parenthesis, followed by it's name. +To create a child class, specify the parent class name inside the pair of parenthesis, followed by its name. Example ```python class Child(Parent): diff --git a/exercises/practice/tree-building/.meta/example.py b/exercises/practice/tree-building/.meta/example.py index e3929ea031..7cf8a6ea90 100644 --- a/exercises/practice/tree-building/.meta/example.py +++ b/exercises/practice/tree-building/.meta/example.py @@ -18,7 +18,7 @@ def validate_record(record): raise ValueError('Only root should have equal record and parent id.') if not record.equal_id() and record.parent_id >= record.record_id: - raise ValueError("Node parent_id should be smaller than it's record_id.") + raise ValueError("Node parent_id should be smaller than its record_id.") def BuildTree(records): diff --git a/exercises/practice/tree-building/tree_building_test.py b/exercises/practice/tree-building/tree_building_test.py index 426ed2b95b..a405aa1ac8 100644 --- a/exercises/practice/tree-building/tree_building_test.py +++ b/exercises/practice/tree-building/tree_building_test.py @@ -111,7 +111,7 @@ def test_root_node_has_parent(self): with self.assertRaises(ValueError) as err: BuildTree(records) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Node parent_id should be smaller than it's record_id.") + self.assertEqual(err.exception.args[0], "Node parent_id should be smaller than its record_id.") def test_no_root_node(self): records = [ @@ -167,7 +167,7 @@ def test_cycle_indirectly(self): with self.assertRaises(ValueError) as err: BuildTree(records) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Node parent_id should be smaller than it's record_id.") + self.assertEqual(err.exception.args[0], "Node parent_id should be smaller than its record_id.") def test_higher_id_parent_of_lower_id(self): records = [ @@ -179,7 +179,7 @@ def test_higher_id_parent_of_lower_id(self): with self.assertRaises(ValueError) as err: BuildTree(records) self.assertEqual(type(err.exception), ValueError) - self.assertEqual(err.exception.args[0], "Node parent_id should be smaller than it's record_id.") + self.assertEqual(err.exception.args[0], "Node parent_id should be smaller than its record_id.") def assert_node_is_branch(self, node, node_id, children_count): self.assertEqual(node.node_id, node_id) From e1566bdff11f722ddcb63e93e466aa822cef05a7 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ali <110213862+abdelrahman495@users.noreply.github.com> Date: Sun, 12 Oct 2025 20:12:13 +0300 Subject: [PATCH 816/826] [Inventory Management & Dicts Concept] Correct Spelling Errors (#4010) * Update introduction.md Fixed a typo for better readability. * Updated Dicts concept about.md Made same spelling change to the concept about.md, which uses the same code examples. --------- Co-authored-by: BethanyG --- concepts/dicts/about.md | 2 +- exercises/concept/inventory-management/.docs/introduction.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/dicts/about.md b/concepts/dicts/about.md index c34160b2ef..72ea9079c6 100644 --- a/concepts/dicts/about.md +++ b/concepts/dicts/about.md @@ -172,7 +172,7 @@ You can change an entry `value` by assigning to its _key_: New `key`:`value` pairs can be _added_ in the same fashion: ```python -# Adding an new "color" key with a new "tawney" value. +# Adding a new "color" key with a new "tawney" value. >>> bear["color"] = 'tawney' {'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True, 'color': 'tawney'} diff --git a/exercises/concept/inventory-management/.docs/introduction.md b/exercises/concept/inventory-management/.docs/introduction.md index 738f36ef75..161b1d0e7c 100644 --- a/exercises/concept/inventory-management/.docs/introduction.md +++ b/exercises/concept/inventory-management/.docs/introduction.md @@ -84,7 +84,7 @@ You can change an entry `value` by assigning to its _key_: New `key`:`value` pairs can be _added_ in the same fashion: ```python -# Adding an new "color" key with a new "tawney" value. +# Adding a new "color" key with a new "tawney" value. >>> bear["color"] = 'tawney' {'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True, 'color': 'tawney'} From 62ac923ed48dfa11e7aa0479f6a5a4e39987b9d4 Mon Sep 17 00:00:00 2001 From: Vidar <01914rifat@tuta.io> Date: Wed, 22 Oct 2025 18:47:00 +0600 Subject: [PATCH 817/826] Fix a typo (#4016) This commit will: - Fix the typo "lean" to "learn" --- docs/LEARNING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/LEARNING.md b/docs/LEARNING.md index 50a3259eed..d71a95455c 100644 --- a/docs/LEARNING.md +++ b/docs/LEARNING.md @@ -6,7 +6,7 @@ Python is (_as [Wikipedia says][wikipython]_), a *general-purpose and high-level It is especially good at 'gluing' different systems and programs together. -And we think the best way to lean is to _play_ and to _practice_ with coding projects big and small - or with small problems like the ones here on exercism! +And we think the best way to learn is to _play_ and to _practice_ with coding projects big and small - or with small problems like the ones here on exercism! Below you will find some additional jumping-off places to start your learning journey, recommended by our community. From 4742d3ec895689cc9a3ef29d9cdcd47b24e75220 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:00:38 -0800 Subject: [PATCH 818/826] Bump actions/checkout from 5.0.0 to 6.0.0 (#4034) Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...1af3b93b6815bc44a9784bd300feb67ff0d1eeb3) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 3a80387e3a..0ad6f1f313 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 5472e7d95e..4f6bff6047 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 428be225ca..97fcf6e5be 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - name: Run test-runner run: docker compose run test-runner From f5ee289c41aadd39db56ffcbcee9fd4fba9243cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:01:15 -0800 Subject: [PATCH 819/826] Bump actions/setup-python from 6.0.0 to 6.1.0 (#4035) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.0.0 to 6.1.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/e797f83bcb11b83ae66e0230d6156d7c80228e7c...83679a892e2d95755f2dac6acb0bfd1e9ac5d548) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 0ad6f1f313..e853469c6d 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 with: python-version: ${{ matrix.python-version }} From dcbfdef0aa7a2fbd5e514347c23636e1c08ab660 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 08:44:26 -0800 Subject: [PATCH 820/826] Bump actions/stale from 10.0.0 to 10.1.0 (#4020) Bumps [actions/stale](https://github.com/actions/stale) from 10.0.0 to 10.1.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/3a9db7e6a41a89f618792c92c0e97cc736e1b13f...5f858e3efba33a5ca4407a664cc011ad407f2008) --- updated-dependencies: - dependency-name: actions/stale dependency-version: 10.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1c4ddca6a9..4a5a9a772f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-24.04 steps: - - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 From d069b5db6e9bd791264946c3683bbbfffab49e28 Mon Sep 17 00:00:00 2001 From: PetreM Date: Sun, 21 Dec 2025 23:25:30 +0200 Subject: [PATCH 821/826] robot-name approach: fix typos, minor rephrasing/improvements (#4051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * robot-name approach: fix typos, minor rephasing/improvements - Fixed a few typos. - Rephrased here and there where I subjectively thought it useful. Please let me know if they're undesirable. - Expanded some contractions - this might help non-native speakers. - When another approach is mentioned, added link to it - hopefully this is useful rather than an distraction - I thought the explanation about the alternative to using the walrus operator was not clear, so rephrased and added a code snippet. I hope this is useful. * Update exercises/practice/robot-name/.approaches/mass-name-generation/content.md Co-authored-by: BethanyG * Update exercises/practice/robot-name/.approaches/mass-name-generation/content.md Co-authored-by: BethanyG * Update exercises/practice/robot-name/.approaches/mass-name-generation/content.md Co-authored-by: BethanyG * Update exercises/practice/robot-name/.approaches/mass-name-generation/content.md Co-authored-by: BethanyG * Update exercises/practice/robot-name/.approaches/name-on-the-fly/content.md Co-authored-by: BethanyG * Update exercises/practice/robot-name/.approaches/name-on-the-fly/content.md Co-authored-by: BethanyG * Update exercises/practice/robot-name/.approaches/mass-name-generation/content.md Co-authored-by: András B Nagy <20251272+BNAndras@users.noreply.github.com> * blank lines after headers and remove trailing whitespace * add blank lines around code snippets and expand one more contraction --------- Co-authored-by: BethanyG Co-authored-by: András B Nagy <20251272+BNAndras@users.noreply.github.com> --- .../robot-name/.approaches/introduction.md | 12 ++++-- .../mass-name-generation/content.md | 21 +++++----- .../.approaches/name-on-the-fly/content.md | 39 +++++++++++++------ 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/exercises/practice/robot-name/.approaches/introduction.md b/exercises/practice/robot-name/.approaches/introduction.md index c4b6738380..d0140e6534 100644 --- a/exercises/practice/robot-name/.approaches/introduction.md +++ b/exercises/practice/robot-name/.approaches/introduction.md @@ -1,12 +1,15 @@ # Introduction -Robot Name in Python is an interesting exercise for practising randomness. + +Robot Name in Python is an interesting exercise for practicing randomness. ## General Guidance -Two ways immedietely come to mind: generate all the possible names and then return them sequentially, or generate a random name and ensure that it's not been previously used. + +Two ways immediately come to mind: generate all the possible names and then return them sequentially, or generate a random name and ensure that it has not been previously used. Randomness can be a little, well, random, so **it's very easy to have an incorrect solution and still pass the tests**. It's strongly recommended to submit your solution for Code Review. ## Approach: mass name generation + We'd first have to generate all the possible names, shuffle them, and then use `next` (the simplest way) or maintain a `current_index` and get the name. Here's a possible way to do it: @@ -26,14 +29,17 @@ class Robot(object): def reset(self): self.name = next(NAMES) ``` + Note that selecting randomly from the list of all names would be incorrect, as there's a possibility of the name being repeated. For more detail and explanation of the code, [read here][approach-mass-name-generation]. ## Approach: name on the fly -Another approach is to generate the name on the fly and add it to a cache or a store, and checking if the generated name hasn't been used previously. + +Another approach is to generate the name on the fly and add it to a cache or a store, checking if the generated name hasn't been used previously. A possible way to implement this: + ```python from string import ascii_uppercase, digits from random import choices diff --git a/exercises/practice/robot-name/.approaches/mass-name-generation/content.md b/exercises/practice/robot-name/.approaches/mass-name-generation/content.md index 392a34ca19..a245195fa5 100644 --- a/exercises/practice/robot-name/.approaches/mass-name-generation/content.md +++ b/exercises/practice/robot-name/.approaches/mass-name-generation/content.md @@ -1,8 +1,9 @@ # Mass Name Generation -We'd first have to generate all the possible names, shuffle them, and then use `next` (the simplest way) or maintain a `current_index` and get the name. -Note that selecting randomly from the list of all names would be incorrect, as there's a possibility of the name being repeated. -Here's a possible way to do it: +We first generate all the possible names, shuffle them, and then either use `next` (the simplest way) or maintain a `current_index` to get the name. +Note that selecting randomly from the list of all names would be incorrect, as there is a possibility of the name being repeated. + +One possible way to do it: ```python from itertools import product @@ -25,25 +26,27 @@ class Robot(object): The first few lines of the mass name generation uses [`itertools.product`][itertools-product]. The resultant code is a simplification of: + ```python letter_pairs = (''.join((l1, l2)) for l1 in ascii_uppercase for l2 in ascii_uppercase) numbers = (str(i).zfill(3) for i in range(1000)) names = [l + n for l in letter_pairs for n in numbers] ``` -After the name generation, the names are shuffled - using the [default `seed`][random-seed] in the `random` module (the current timestamp). +After the name generation, the names are shuffled - using the [default `seed`][random-seed] in the `random` module (the current timestamp). When the tests reseed `random`, this has no effect as the names were shuffled before that. -We then set `NAMES` to the iterable of names, and in `reset`, set the robot's name to the `next(name)`. -If you'd like, read more on [`iter` and `next`][iter-and-next]. +We then set `NAMES` to the iterable of names, and in `reset`, set the robot's name to the `next(name)`. +If you are interested, you can read more on [`iter` and `next`][iter-and-next]. -Unlike the on the fly approach, this has a relatively short "generation" time, because we're merely giving the `next` name instead of generating it. -However, this has a huge startup memory and time cost, as 676,000 strings have to be calculated and stored. +Unlike the [on the fly approach][approach-name-on-the-fly], this has a relatively short "generation" time, because we are merely giving the `next` name instead of generating it. +However, this has a huge startup memory and time cost, as 676,000 strings have to be calculated and stored. For an approximate calculation, 676,000 strings * 5 characters / string * 1 byte / character gives 3380000 bytes or 3.38 MB of RAM - and that's just the memory aspect of it. -Sounds small, but it's relatively very expensive at the beginning. +Sounds small, but this might be a relatively significant startup cost. Thus, this approach is inefficient in cases where only a small number of names are needed _and_ the time to set/reset the robot isn't crucial. [random-seed]: https://docs.python.org/3/library/random.html#random.seed [iter-and-next]: https://www.programiz.com/python-programming/methods/built-in/iter [itertools-product]: https://www.hackerrank.com/challenges/itertools-product/problem +[approach-name-on-the-fly]: https://exercism.org/tracks/python/exercises/robot-name/approaches/name-on-the-fly diff --git a/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md b/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md index 0aa9f9a3fa..494b32b2d1 100644 --- a/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md +++ b/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md @@ -1,7 +1,9 @@ # Find name on the fly -We generate the name on the fly and add it to a cache or a store, and checking if the generated name hasn't been used previously. + +We generate the name on the fly and add it to a cache or a store, checking to make sure that the generated name has not been used previously. A possible way to implement this: + ```python from string import ascii_uppercase, digits from random import choices @@ -10,7 +12,7 @@ cache = set() class Robot: - def __get_name(self): + def __get_name(self): return ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3)) def reset(self): @@ -19,18 +21,30 @@ class Robot: cache.add(name) self.name = name - def __init__(self): + def __init__(self): self.reset() ``` -We use a `set` for the cache as it has a low access time, and we don't need the preservation of order or the ability to be indexed. -This way is merely one of the many to generate the name. +We use a `set` for the cache as it has a low access time, and because we do not need the preservation of order or the ability to access by index. + +Using `choices` is one of the many ways to generate the name. Another way might be to use `randrange` along with `zfill` for the number part, and a double `random.choice` / `random.choice` on `itertools.product` to generate the letter part. -This is the shortest way, and best utilizes the Python standard library. +The first is shorter, and best utilizes the Python standard library. + +As we are using a `while` loop to check for the name generation, it is convenient to store the local `name` using the [walrus operator][walrus-operator]. +It's also possible to find the name once before the loop, and then find it again inside the loop, but that would be an unnecessary repetition: + +```python +def reset(self): + name = self.__get_name() + while name in cache: + name = self.__get_name() + cache.add(name) + self.name = name +``` -As we're using a `while` loop to check for the name generation, it's convenient to store the local `name` using the [walrus operator][walrus-operator]. -It's also possible to find the name before the loop and find it again inside the loop, but that would unnecessary repetition. A helper method ([private][private-helper-methods] in this case) makes your code cleaner, but it's equally valid to have the code in the loop itself: + ```python def reset(self): while (name := ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3))) in cache: @@ -39,14 +53,15 @@ def reset(self): self.name = name ``` -We call `reset` from `__init__` - it's syntactically valid to do it the other way round, but it's not considered good practice to call [dunder methods][dunder-methods] directly. +We call `reset` from `__init__` - it is syntactically valid to do it the other way around, but it is not considered good practice to call [dunder methods][dunder-methods] directly. This has almost no startup time and memory, apart from declaring an empty `set`. -Note that the _generation_ time is the same as the mass generation approach, as a similar method is used. +Note that the _generation_ time is the same as the [mass generation approach][approach-mass-name-generation], as a similar method is used. However, as the name is generated at the time of setting/resetting, the method time itself is higher. -In the long run, if many names are generated, this is inefficient, since collisions will start being generated more often than unique names. +In the long run, if many names are generated, this is inefficient, since collisions will start being generated more often than unique names. [walrus-operator]: https://realpython.com/python-walrus-operator/ [private-helper-methods]: https://www.geeksforgeeks.org/private-methods-in-python/ -[dunder-methods]: https://dbader.org/blog/python-dunder-methods \ No newline at end of file +[dunder-methods]: https://dbader.org/blog/python-dunder-methods +[approach-mass-name-generation]: https://exercism.org/tracks/python/exercises/robot-name/approaches/mass-name-generation From 6865784f3f7abda58aa593668cb312ebd4921cc0 Mon Sep 17 00:00:00 2001 From: PetreM Date: Thu, 25 Dec 2025 00:37:50 +0200 Subject: [PATCH 822/826] rna-transcription approach: a few improvements (#4052) * rna-transcription approach: a few improvements - Renamed `chr` to `char` in the snippets because `chr` is a built-in function and although shadowing it in this case may not be a problem, it is still a bad practice when it can be avoided. - The dictionary-join approach mentions list comprehensions, but instead it uses a generator expression. Replaced this in the explanation and expanded to give the list comprehension based implementation along with a brief comparison. - The overview mentions one approach is four times faster. In a brief comparison, it varies from 2.5x for a very short string and up to 60x faster for a 10^6 long one. Probably not worth going into the details, but 4x is just innacurate. * convert code snippets to single quotes for consistency * several updates following discussions - Replaced `char` with `nucleotide` as this is terminology from the domain. - Rephrased a "see also" link to be more screen reader friendly. - A note about the exercise not requiring tests for invalid characters is preset in one of the approaches. Copied it over to the other approach, for uniformity. - Rephrased mention about performance and speedup. - Replaced mention of ASCII with Unicode adding a brief explanation and links. * move note regarding testing for erroneous inputs to `introduction.md` ... because it applies to the exercise in general, not a particular approach. Re-applying missed commits from prior cherry-pick. * Re-applied the commits from below via cherry-pick. convert code snippets to single quotes for consistency --- .../.approaches/dictionary-join/content.md | 32 ++++++++++++++++--- .../.approaches/dictionary-join/snippet.txt | 4 +-- .../.approaches/introduction.md | 18 +++++++---- .../translate-maketrans/content.md | 17 +++++----- .../translate-maketrans/snippet.txt | 2 +- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/exercises/practice/rna-transcription/.approaches/dictionary-join/content.md b/exercises/practice/rna-transcription/.approaches/dictionary-join/content.md index f3ec1f755f..fcf0c58953 100644 --- a/exercises/practice/rna-transcription/.approaches/dictionary-join/content.md +++ b/exercises/practice/rna-transcription/.approaches/dictionary-join/content.md @@ -1,11 +1,11 @@ # dictionary look-up with `join` ```python -LOOKUP = {"G": "C", "C": "G", "T": "A", "A": "U"} +LOOKUP = {'G': 'C', 'C': 'G', 'T': 'A', 'A': 'U'} def to_rna(dna_strand): - return ''.join(LOOKUP[chr] for chr in dna_strand) + return ''.join(LOOKUP[nucleotide] for nucleotide in dna_strand) ``` @@ -16,15 +16,37 @@ but the `LOOKUP` dictionary is defined with all uppercase letters, which is the It indicates that the value is not intended to be changed. In the `to_rna()` function, the [`join()`][join] method is called on an empty string, -and is passed the list created from a [list comprehension][list-comprehension]. +and is passed the list created from a [generator expression][generator-expression]. -The list comprehension iterates each character in the input, +The generator expression iterates each character in the input, looks up the DNA character in the look-up dictionary, and outputs its matching RNA character as an element in the list. -The `join()` method collects the list of RNA characters back into a string. +The `join()` method collects the RNA characters back into a string. Since an empty string is the separator for the `join()`, there are no spaces between the RNA characters in the string. +A generator expression is similar to a [list comprehension][list-comprehension], but instead of creating a list, it returns a generator, and iterating that generator yields the elements on the fly. + +A variant that uses a list comprehension is almost identical, but note the additional square brackets inside the `join()`: + +```python +LOOKUP = {'G': 'C', 'C': 'G', 'T': 'A', 'A': 'U'} + +def to_rna(dna_strand): + return ''.join([LOOKUP[nucleotide] for nucleotide in dna_strand]) +``` + + +For a relatively small number of elements, using lists is fine and may be faster, but as the number of elements increases, the memory consumption increases and performance decreases. +You can read more about [when to choose generators over list comprehensions][list-comprehension-choose-generator-expression] to dig deeper into the topic. + + +~~~~exercism/note +As of this writing, no invalid DNA characters are in the argument to `to_rna()`, so there is no error handling required for invalid input. +~~~~ + [dictionaries]: https://docs.python.org/3/tutorial/datastructures.html?#dictionaries [const]: https://realpython.com/python-constants/ [join]: https://docs.python.org/3/library/stdtypes.html?#str.join [list-comprehension]: https://realpython.com/list-comprehension-python/#using-list-comprehensions +[list-comprehension-choose-generator-expression]: https://realpython.com/list-comprehension-python/#choose-generators-for-large-datasets +[generator-expression]: https://realpython.com/introduction-to-python-generators/#building-generators-with-generator-expressions diff --git a/exercises/practice/rna-transcription/.approaches/dictionary-join/snippet.txt b/exercises/practice/rna-transcription/.approaches/dictionary-join/snippet.txt index 558bf98140..398f2dfb07 100644 --- a/exercises/practice/rna-transcription/.approaches/dictionary-join/snippet.txt +++ b/exercises/practice/rna-transcription/.approaches/dictionary-join/snippet.txt @@ -1,5 +1,5 @@ -LOOKUP = {"G": "C", "C": "G", "T": "A", "A": "U"} +LOOKUP = {'G': 'C', 'C': 'G', 'T': 'A', 'A': 'U'} def to_rna(dna_strand): - return ''.join(LOOKUP[chr] for chr in dna_strand) + return ''.join(LOOKUP[nucleotide] for nucleotide in dna_strand) diff --git a/exercises/practice/rna-transcription/.approaches/introduction.md b/exercises/practice/rna-transcription/.approaches/introduction.md index ca2d74a109..54b4c1f7d3 100644 --- a/exercises/practice/rna-transcription/.approaches/introduction.md +++ b/exercises/practice/rna-transcription/.approaches/introduction.md @@ -7,13 +7,13 @@ Another approach is to do a dictionary lookup on each character and join the res ## General guidance Whichever approach is used needs to return the RNA complement for each DNA value. -The `translate()` method with `maketrans()` transcribes using the [ASCII][ASCII] values of the characters. +The `translate()` method with `maketrans()` transcribes using the [Unicode][Unicode] code points of the characters. Using a dictionary look-up with `join()` transcribes using the string values of the characters. ## Approach: `translate()` with `maketrans()` ```python -LOOKUP = str.maketrans("GCTA", "CGAU") +LOOKUP = str.maketrans('GCTA', 'CGAU') def to_rna(dna_strand): @@ -26,11 +26,11 @@ For more information, check the [`translate()` with `maketrans()` approach][appr ## Approach: dictionary look-up with `join()` ```python -LOOKUP = {"G": "C", "C": "G", "T": "A", "A": "U"} +LOOKUP = {'G': 'C', 'C': 'G', 'T': 'A', 'A': 'U'} def to_rna(dna_strand): - return ''.join(LOOKUP[chr] for chr in dna_strand) + return ''.join(LOOKUP[nucleotide] for nucleotide in dna_strand) ``` @@ -38,8 +38,14 @@ For more information, check the [dictionary look-up with `join()` approach][appr ## Which approach to use? -The `translate()` with `maketrans()` approach benchmarked over four times faster than the dictionary look-up with `join()` approach. +If performance matters, consider using the [`translate()` with `maketrans()` approach][approach-translate-maketrans]. +How an implementation behaves in terms of performance may depend on the actual data being processed, on hardware, and other factors. -[ASCII]: https://www.asciitable.com/ + +~~~~exercism/note +As of this writing, no invalid DNA characters are in the argument to `to_rna()`, so there is no error handling required for invalid input. +~~~~ + +[Unicode]: https://en.wikipedia.org/wiki/Unicode [approach-translate-maketrans]: https://exercism.org/tracks/python/exercises/rna-transcription/approaches/translate-maketrans [approach-dictionary-join]: https://exercism.org/tracks/python/exercises/rna-transcription/approaches/dictionary-join diff --git a/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md b/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md index 9b484e3cb5..9373cf12b2 100644 --- a/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md +++ b/exercises/practice/rna-transcription/.approaches/translate-maketrans/content.md @@ -1,7 +1,7 @@ # `translate()` with `maketrans()` ```python -LOOKUP = str.maketrans("GCTA", "CGAU") +LOOKUP = str.maketrans('GCTA', 'CGAU') def to_rna(dna_strand): @@ -15,20 +15,21 @@ Python doesn't _enforce_ having real constant values, but the `LOOKUP` translation table is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. It indicates that the value is not intended to be changed. -The translation table that is created uses the [ASCII][ASCII] values (also called the ordinal values) for each letter in the two strings. -The ASCII value for "G" in the first string is the key for the ASCII value of "C" in the second string, and so on. +The translation table that is created uses the [Unicode][Unicode] _code points_ (sometimes called the ordinal values) for each letter in the two strings. +As Unicode was designed to be backwards compatible with [ASCII][ASCII] and because the exercise uses Latin letters, the code points in the translation table can be interpreted as ASCII. +However, the functions can deal with any Unicode character. +You can learn more by reading about [strings and their representation in the Exercism Python syllabus][concept-string]. + +The Unicode value for "G" in the first string is the key for the Unicode value of "C" in the second string, and so on. In the `to_rna()` function, the [`translate()`][translate] method is called on the input, and is passed the translation table. The output of `translate()` is a string where all of the input DNA characters have been replaced by their RNA complement in the translation table. - -~~~~exercism/note -As of this writing, no invalid DNA characters are in the argument to `to_rna()`, so there is no error handling required for invalid input. -~~~~ - [dictionaries]: https://docs.python.org/3/tutorial/datastructures.html?#dictionaries [maketrans]: https://docs.python.org/3/library/stdtypes.html?#str.maketrans [const]: https://realpython.com/python-constants/ [translate]: https://docs.python.org/3/library/stdtypes.html?#str.translate [ASCII]: https://www.asciitable.com/ +[Unicode]: https://en.wikipedia.org/wiki/Unicode +[concept-strings]: https://exercism.org/tracks/python/concepts/strings diff --git a/exercises/practice/rna-transcription/.approaches/translate-maketrans/snippet.txt b/exercises/practice/rna-transcription/.approaches/translate-maketrans/snippet.txt index 2d00b83be6..db15d868f1 100644 --- a/exercises/practice/rna-transcription/.approaches/translate-maketrans/snippet.txt +++ b/exercises/practice/rna-transcription/.approaches/translate-maketrans/snippet.txt @@ -1,4 +1,4 @@ -LOOKUP = str.maketrans("GCTA", "CGAU") +LOOKUP = str.maketrans('GCTA', 'CGAU') def to_rna(dna_strand): From 0410dcc8527539be0f5707f2079bd888f2e0888c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 30 Dec 2025 13:55:45 -0800 Subject: [PATCH 823/826] December 2025 practice exercise docs sync. (#4058) Pulled in latest exercise introductions and instructions from problem-specifications. --- .../.docs/instructions.md | 2 +- .../ocr-numbers/.docs/instructions.md | 80 ++++++------------- .../ocr-numbers/.docs/introduction.md | 6 ++ .../practice/triangle/.docs/instructions.md | 3 +- 4 files changed, 33 insertions(+), 58 deletions(-) create mode 100644 exercises/practice/ocr-numbers/.docs/introduction.md diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md index fdafdca8fb..9f5cb1368f 100644 --- a/exercises/practice/killer-sudoku-helper/.docs/instructions.md +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -74,7 +74,7 @@ You can also find Killer Sudokus in varying difficulty in numerous newspapers, a ## Credit -The screenshots above have been generated using [F-Puzzles.com](https://www.f-puzzles.com/), a Puzzle Setting Tool by Eric Fox. +The screenshots above have been generated using F-Puzzles.com, a Puzzle Setting Tool by Eric Fox. [sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/ [killer-guide]: https://masteringsudoku.com/killer-sudoku/ diff --git a/exercises/practice/ocr-numbers/.docs/instructions.md b/exercises/practice/ocr-numbers/.docs/instructions.md index 7beb257795..8a391ce4f6 100644 --- a/exercises/practice/ocr-numbers/.docs/instructions.md +++ b/exercises/practice/ocr-numbers/.docs/instructions.md @@ -1,79 +1,47 @@ # Instructions -Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled. +Optical Character Recognition or OCR is software that converts images of text into machine-readable text. +Given a grid of characters representing some digits, convert the grid to a string of digits. +If the grid has multiple rows of cells, the rows should be separated in the output with a `","`. -## Step One +- The grid is made of one of more lines of cells. +- Each line of the grid is made of one or more cells. +- Each cell is three columns wide and four rows high (3x4) and represents one digit. +- Digits are drawn using pipes (`"|"`), underscores (`"_"`), and spaces (`" "`). -To begin with, convert a simple binary font to a string containing 0 or 1. +## Edge cases -The binary font uses pipes and underscores, four rows high and three columns wide. +- If the input is not a valid size, your program should indicate there is an error. +- If the input is the correct size, but a cell is not recognizable, your program should output a `"?"` for that character. -```text - _ # - | | # zero. - |_| # - # the fourth row is always blank -``` +## Examples -Is converted to "0" - -```text - # - | # one. - | # - # (blank fourth row) -``` - -Is converted to "1" - -If the input is the correct size, but not recognizable, your program should return '?' - -If the input is the incorrect size, your program should return an error. - -## Step Two - -Update your program to recognize multi-character binary strings, replacing garbled numbers with ? - -## Step Three - -Update your program to recognize all numbers 0 through 9, both individually and as part of a larger string. - -```text - _ - _| -|_ - -``` - -Is converted to "2" +The following input (without the comments) is converted to `"1234567890"`. ```text _ _ _ _ _ _ _ _ # - | _| _||_||_ |_ ||_||_|| | # decimal numbers. + | _| _||_||_ |_ ||_||_|| | # Decimal numbers. ||_ _| | _||_| ||_| _||_| # - # fourth line is always blank + # The fourth line is always blank, ``` -Is converted to "1234567890" - -## Step Four +The following input is converted to `"123,456,789"`. -Update your program to handle multiple numbers, one per line. -When converting several lines, join the lines with commas. + ```text - _ _ + _ _ | _| _| ||_ _| - - _ _ -|_||_ |_ + + _ _ +|_||_ |_ | _||_| - - _ _ _ + + _ _ _ ||_||_| ||_| _| - + ``` -Is converted to "123,456,789". + diff --git a/exercises/practice/ocr-numbers/.docs/introduction.md b/exercises/practice/ocr-numbers/.docs/introduction.md new file mode 100644 index 0000000000..366d76062c --- /dev/null +++ b/exercises/practice/ocr-numbers/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +Your best friend Marta recently landed their dream job working with a local history museum's collections. +Knowing of your interests in programming, they confide in you about an issue at work for an upcoming exhibit on computing history. +A local university's math department had donated several boxes of historical printouts, but given the poor condition of the documents, the decision has been made to digitize the text. +However, the university's old printer had some quirks in how text was represented, and your friend could use your help to extract the data successfully. diff --git a/exercises/practice/triangle/.docs/instructions.md b/exercises/practice/triangle/.docs/instructions.md index 755cb8d19d..e9b053dcd3 100644 --- a/exercises/practice/triangle/.docs/instructions.md +++ b/exercises/practice/triangle/.docs/instructions.md @@ -14,7 +14,8 @@ A _scalene_ triangle has all sides of different lengths. For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. ~~~~exercism/note -We opted to not include tests for degenerate triangles (triangles that violate these rules) to keep things simpler. +_Degenerate triangles_ are triangles where the sum of the length of two sides is **equal** to the length of the third side, e.g. `1, 1, 2`. +We opted to not include tests for degenerate triangles in this exercise. You may handle those situations if you wish to do so, or safely ignore them. ~~~~ From a0c1db908af44cadf8771973124bd0063a1eca00 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 30 Dec 2025 13:56:58 -0800 Subject: [PATCH 824/826] Synced tests to problem-specifications and regenerated test cases. (#4059) Added new test cases from problem-specifications and regenerated test file. --- exercises/practice/flower-field/.meta/tests.toml | 3 +++ exercises/practice/flower-field/flower_field_test.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml index c2b24fdaf5..965ba8fd4d 100644 --- a/exercises/practice/flower-field/.meta/tests.toml +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -44,3 +44,6 @@ description = "cross" [dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] description = "large garden" + +[6e4ac13a-3e43-4728-a2e3-3551d4b1a996] +description = "multiple adjacent flowers" diff --git a/exercises/practice/flower-field/flower_field_test.py b/exercises/practice/flower-field/flower_field_test.py index 019f7357fd..d0f1334cbf 100644 --- a/exercises/practice/flower-field/flower_field_test.py +++ b/exercises/practice/flower-field/flower_field_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flower-field/canonical-data.json -# File last updated on 2025-06-25 +# File last updated on 2025-12-30 import unittest @@ -52,6 +52,9 @@ def test_large_garden(self): ["1*22*1", "12*322", " 123*2", "112*4*", "1*22*2", "111111"], ) + def test_multiple_adjacent_flowers(self): + self.assertEqual(annotate([" ** "]), ["1**1"]) + # Additional tests for this track def test_annotate_9(self): self.assertEqual( From e14ea7fb90db29b14eb321f067fe012a0c55c84b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 30 Dec 2025 13:57:59 -0800 Subject: [PATCH 825/826] Updated tests.toml and regenerated isbn-verifier test cases. (#4060) Added two new test cases from problem-specifications. --- exercises/practice/isbn-verifier/.meta/tests.toml | 6 ++++++ exercises/practice/isbn-verifier/isbn_verifier_test.py | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/exercises/practice/isbn-verifier/.meta/tests.toml b/exercises/practice/isbn-verifier/.meta/tests.toml index 6d5a845990..17e18d47ac 100644 --- a/exercises/practice/isbn-verifier/.meta/tests.toml +++ b/exercises/practice/isbn-verifier/.meta/tests.toml @@ -30,6 +30,12 @@ description = "invalid character in isbn is not treated as zero" [28025280-2c39-4092-9719-f3234b89c627] description = "X is only valid as a check digit" +[8005b57f-f194-44ee-88d2-a77ac4142591] +description = "only one check digit is allowed" + +[fdb14c99-4cf8-43c5-b06d-eb1638eff343] +description = "X is not substituted by the value 10" + [f6294e61-7e79-46b3-977b-f48789a4945b] description = "valid isbn without separating dashes" diff --git a/exercises/practice/isbn-verifier/isbn_verifier_test.py b/exercises/practice/isbn-verifier/isbn_verifier_test.py index dbcddf19d4..5c9bf6f755 100644 --- a/exercises/practice/isbn-verifier/isbn_verifier_test.py +++ b/exercises/practice/isbn-verifier/isbn_verifier_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isbn-verifier/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-12-30 import unittest @@ -31,6 +31,12 @@ def test_invalid_character_in_isbn_is_not_treated_as_zero(self): def test_x_is_only_valid_as_a_check_digit(self): self.assertIs(is_valid("3-598-2X507-9"), False) + def test_only_one_check_digit_is_allowed(self): + self.assertIs(is_valid("3-598-21508-96"), False) + + def test_x_is_not_substituted_by_the_value_10(self): + self.assertIs(is_valid("3-598-2X507-5"), False) + def test_valid_isbn_without_separating_dashes(self): self.assertIs(is_valid("3598215088"), True) From 0792f3e139854149767b6b54894a403e9b167063 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 30 Dec 2025 13:59:14 -0800 Subject: [PATCH 826/826] Synced tests.toml and regenerated test cases. (#4061) Added three new test cases from problem-specifications. --- exercises/practice/satellite/.meta/tests.toml | 22 +++++++- .../practice/satellite/satellite_test.py | 55 ++++++++++++++++++- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/exercises/practice/satellite/.meta/tests.toml b/exercises/practice/satellite/.meta/tests.toml index 8314daa436..d0ed5b6ac5 100644 --- a/exercises/practice/satellite/.meta/tests.toml +++ b/exercises/practice/satellite/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [8df3fa26-811a-4165-9286-ff9ac0850d19] description = "Empty tree" @@ -19,3 +26,12 @@ description = "Reject inconsistent traversals of same length" [d86a3d72-76a9-43b5-9d3a-e64cb1216035] description = "Reject traversals with repeated items" + +[af31ae02-7e5b-4452-a990-bccb3fca9148] +description = "A degenerate binary tree" + +[ee54463d-a719-4aae-ade4-190d30ce7320] +description = "Another degenerate binary tree" + +[87123c08-c155-4486-90a4-e2f75b0f3e8f] +description = "Tree with many more items" diff --git a/exercises/practice/satellite/satellite_test.py b/exercises/practice/satellite/satellite_test.py index f44a538479..6b960de73e 100644 --- a/exercises/practice/satellite/satellite_test.py +++ b/exercises/practice/satellite/satellite_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/satellite/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-12-30 import unittest @@ -67,3 +67,56 @@ def test_reject_traversals_with_repeated_items(self): tree_from_traversals(preorder, inorder) self.assertEqual(type(err.exception), ValueError) self.assertEqual(err.exception.args[0], "traversals must contain unique items") + + def test_a_degenerate_binary_tree(self): + preorder = ["a", "b", "c", "d"] + inorder = ["d", "c", "b", "a"] + + expected = { + "v": "a", + "l": { + "v": "b", + "l": {"v": "c", "l": {"v": "d", "l": {}, "r": {}}, "r": {}}, + "r": {}, + }, + "r": {}, + } + self.assertEqual(tree_from_traversals(preorder, inorder), expected) + + def test_another_degenerate_binary_tree(self): + preorder = ["a", "b", "c", "d"] + inorder = ["a", "b", "c", "d"] + + expected = { + "v": "a", + "l": {}, + "r": { + "v": "b", + "l": {}, + "r": {"v": "c", "l": {}, "r": {"v": "d", "l": {}, "r": {}}}, + }, + } + self.assertEqual(tree_from_traversals(preorder, inorder), expected) + + def test_tree_with_many_more_items(self): + preorder = ["a", "b", "d", "g", "h", "c", "e", "f", "i"] + inorder = ["g", "d", "h", "b", "a", "e", "c", "i", "f"] + + expected = { + "v": "a", + "l": { + "v": "b", + "l": { + "v": "d", + "l": {"v": "g", "l": {}, "r": {}}, + "r": {"v": "h", "l": {}, "r": {}}, + }, + "r": {}, + }, + "r": { + "v": "c", + "l": {"v": "e", "l": {}, "r": {}}, + "r": {"v": "f", "l": {"v": "i", "l": {}, "r": {}}, "r": {}}, + }, + } + self.assertEqual(tree_from_traversals(preorder, inorder), expected)