diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml
index fe8ff089a67..01ddab0cac5 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/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 4742a0db0a0..41156435d82 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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 691ca9f92fc..0bac23e0af3 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.
@@ -196,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.
@@ -228,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.
@@ -372,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
@@ -381,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
diff --git a/README.md b/README.md
index 64930816362..1c82f33b9d0 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/bin/data.py b/bin/data.py
index 54dec7612b3..7fce51b9087 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 d0406aad5e2..6c9b1c38b98 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
@@ -34,11 +35,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
@@ -254,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():
@@ -264,20 +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:
- current_lines = f.readlines()
+ current_lines = f.readlines()[3:]
with tmpfile.open() as 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"
@@ -387,6 +412,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/concepts/basics/about.md b/concepts/basics/about.md
index 929582357c8..30ea083022f 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 e48e6726515..34e2a8804d7 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/concepts/binary-octal-hexadecimal/.meta/config.json b/concepts/binary-octal-hexadecimal/.meta/config.json
new file mode 100644
index 00000000000..6e2a15b607a
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/.meta/config.json
@@ -0,0 +1,4 @@
+{
+ "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
new file mode 100644
index 00000000000..667f3eb6cd7
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/about.md
@@ -0,0 +1,224 @@
+# 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 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, 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:
+
+| 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 literals 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 just a different way of writing an integer and so the binary representation **is an integer** for all mathematical operations.
+
+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):
+ File "c:\binary.py", line 1, in
+ 0b10211
+SyntaxError: invalid digit '2' in binary literal
+```
+
+### Operations with Binary Numbers
+
+Since binary numbers are integers, we can perform all operations on them that we can with integers.
+
+```python
+# addition
+>>> 0b10011 + 0b10011
+38
+
+# multiplication
+>>> 0b10011 * 0b10011
+361
+```
+
+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
+
+>>> 0b10011/0b10011
+1.0
+
+>>> 0b10011/3
+6.333333333333333
+
+### Converting to and from Binary Representation
+
+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(19)
+'0b10011'
+```
+
+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 (_or an invalid binary representation_) will raise a `ValueError`:
+
+```python
+Traceback (most recent call last):
+ File "c:\binary.py", line 4, in
+ int("0b10011", 3)
+ValueError: invalid literal for int() with base 3: '0b10011'
+```
+
+### Binary Methods
+
+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:
+
+```python
+>>> 0b11011.bit_length()
+5
+```
+
+
+[`.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.
+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 automatically converts an octal representation to an `int`.
+
+```python
+# 0o123
+>>> 0o123
+83
+```
+
+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`.
+
+ ### Converting to and from Octal Representation
+
+
+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(83)
+'0o123'
+
+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)
+83
+```
+
+As with binary, giving the wrong base will raise a `ValueError`.
+
+### Hexadecimal
+
+[Hexadecimal][hexadecimal] is a base 16 numeral system.
+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.
+
+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
+# 0x123
+>>> 0x123
+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`.
+
+
+### 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(291)
+'0x123'
+
+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)
+291
+```
+
+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
new file mode 100644
index 00000000000..820aac33ee7
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/introduction.md
@@ -0,0 +1,11 @@
+# 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 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
diff --git a/concepts/binary-octal-hexadecimal/links.json b/concepts/binary-octal-hexadecimal/links.json
new file mode 100644
index 00000000000..8826182cd48
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/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"
+ }
+]
diff --git a/concepts/comparisons/about.md b/concepts/comparisons/about.md
index 568b6603c1a..c2f5faaad91 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/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/concepts/dict-methods/about.md b/concepts/dict-methods/about.md
index e36fc7082e1..d910d3e9168 100644
--- a/concepts/dict-methods/about.md
+++ b/concepts/dict-methods/about.md
@@ -1,74 +1,75 @@
# 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.
-
-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.
-
-## 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()` 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.
-```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.'
+## `fromkeys()` to Populate a Dictionary from an Iterable
->>> 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`.
+## 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.
+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 +78,38 @@ 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 Via Views
+
+The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary.
+
+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'}
+>>> 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 +117,69 @@ 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 when 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')
```
-Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`.
+## 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 ``:
```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'}
+>>> 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),
@@ -137,14 +188,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.
+## 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`.
+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 +212,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 +248,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 +257,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 +269,93 @@ 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
+
+>>> swapped_color_reference
+{'#8076a3': 'Purple Mountains Majesty',
+ '#f9c5bd': 'Misty Mountain Pink',
+ '#7c677f': 'Factory Stone Purple',
+ '#478559': 'Green Treeline',
+ '#161748': 'Purple baseline'}
->>> reversed_color_reference
+
+# 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',
- '#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'}
+```
+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
[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
+[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
diff --git a/concepts/dict-methods/introduction.md b/concepts/dict-methods/introduction.md
index 52868299b9d..c15fbc113de 100644
--- a/concepts/dict-methods/introduction.md
+++ b/concepts/dict-methods/introduction.md
@@ -1,17 +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 [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.
+The `dict` class in Python provides many useful [methods][dict-methods], some of which are introduced in the concept exercise for dictionaries.
-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.
+This concept tackles a few more:
-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`.
+- `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.
-[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 fb43067efe2..c34160b2ef6 100644
--- a/concepts/dicts/about.md
+++ b/concepts/dicts/about.md
@@ -1,55 +1,292 @@
# 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`.
+`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.
- 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.
+`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.
-## Dictionary creation
+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.
-A simple `dict` can be declared using the literal form `{: , : }`:
+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, 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`.
+The two most straightforward methods are the dictionary _constructor_ and the dictionary _literal_.
+### The Dictionary Constructor
+
+`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.
+>>> 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'}
+```
+
+### Nested Dictionaries
+
+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'
+```
+
+### 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.
+>>> 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'}
+
+
+# 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
+>>> del wombat["number_of_legs"]
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 'number_of_legs'
+```
-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]`.
+## Looping Through/Iterating over a Dictionary
-## Methods
+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_:
-`dicts` implement various methods to allow easy initialization, updating and viewing.
+```python
+>>> for key in bear:
+>>> print((key, bear[key])) #this prints a tuple of (key, value)
+('name', 'Black Bear')
+('speed', 40)
+('land_animal', True)
+```
-Some useful `dict` methods:
+You can also use the `.items()` method, which returns (`key`, `value`) tuples:
-- 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`_).
+```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`).
+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
[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 9a887ccfc65..5c8a772480b 100644
--- a/concepts/dicts/introduction.md
+++ b/concepts/dicts/introduction.md
@@ -1,15 +1,24 @@
# 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/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json
index 2204a700df6..6c29169d3f4 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/about.md b/concepts/generators/about.md
index f1b97291e4c..9a26ab55480 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.
@@ -96,14 +94,16 @@ 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():
->>> 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 +123,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 2c148371330..82a686d1e05 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 b8ae2f7b648..972bbe7ae97 100644
--- a/concepts/generators/links.json
+++ b/concepts/generators/links.json
@@ -1,10 +1,14 @@
[
{
- "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.10/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://realpython.com/introduction-to-python-generators/",
+ "description": "Real python, introduction to generators and yield"
}
]
diff --git a/concepts/sets/about.md b/concepts/sets/about.md
index dd567f070f4..5f272402eef 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/concepts/string-formatting/about.md b/concepts/string-formatting/about.md
index 07306a8d8bf..f3b2756b768 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,31 +137,28 @@ 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.
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_.
-
+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?
diff --git a/concepts/string-methods/about.md b/concepts/string-methods/about.md
index 81eaffb0a7a..e01cbaf8908 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/concepts/strings/about.md b/concepts/strings/about.md
index 895bad45b42..0107f6e70f0 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:
diff --git a/concepts/tuples/links.json b/concepts/tuples/links.json
index a6a22b9b8fa..a0d85de8a5a 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/",
diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md
index e8abaa528d7..1cec2f92ec9 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 a4675a771ed..59cab3b4ec3 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/config.json b/config.json
index dbafb1ef987..36f517f9495 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"],
@@ -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",
@@ -184,7 +192,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"
},
{
@@ -683,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",
@@ -858,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",
@@ -970,6 +978,21 @@
],
"difficulty": 3
},
+ {
+ "slug": "resistor-color-expert",
+ "name": "Resistor Color Expert",
+ "uuid": "8a738365-0efa-444f-9466-a757ddaddcdb",
+ "practices": ["list-methods"],
+ "prerequisites": [
+ "basics",
+ "bools",
+ "lists",
+ "numbers",
+ "strings",
+ "comparisons"
+ ],
+ "difficulty": 3
+ },
{
"slug": "matrix",
"name": "Matrix",
@@ -1247,14 +1270,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",
@@ -1441,7 +1456,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",
@@ -2201,6 +2216,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",
@@ -2234,6 +2258,11 @@
"slug": "binary-data",
"name": "Binary Data"
},
+ {
+ "uuid": "78dbf248-a1e5-48cb-ba53-def4d92bf2a8",
+ "slug": "binary-octal-hexadecimal",
+ "name": "Binary, Octal, and Hexadecimal"
+ },
{
"uuid": "f8e96e60-f746-4c7a-8dc9-df6b77eefdfa",
"slug": "bitflags",
@@ -2306,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/config/complexnumbers_template.j2 b/config/complexnumbers_template.j2
new file mode 100644
index 00000000000..d70c866f1c5
--- /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 76a8c07230e..b1927552927 100644
--- a/config/generator_macros.j2
+++ b/config/generator_macros.j2
@@ -10,10 +10,13 @@
{% 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=[]) -%}
+
import unittest
from {{ exercise | to_snake }} import ({% if imports -%}
@@ -27,8 +30,6 @@ from {{ exercise | to_snake }} import ({% if imports -%}
{%- endif -%}
{% endfor %}
{%- endif %})
-
-{{ canonical_ref() }}
{%- endmacro %}
{% macro utility() -%}# Utility functions
@@ -36,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 4fe2b9798b5..774771fd5d1 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/docs/ABOUT.md b/docs/ABOUT.md
index 9b53653fd17..6177394a518 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.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 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+`, 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.9.7) 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]
@@ -36,6 +37,8 @@ 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 d1a8a1da495..271c03d71f1 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 9481ce512e3..04f9c899349 100644
--- a/docs/INSTALLATION.md
+++ b/docs/INSTALLATION.md
@@ -6,8 +6,8 @@ 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.
-You should instead install Python 3 via one of the methods detailed below.
+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.
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.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+`, or even earlier versions.
+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 15e4fd80215..50a3259eed7 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
@@ -36,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 9e522b12d57..242555371f3 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
@@ -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/docs/TOOLS.md b/docs/TOOLS.md
index f5fdedcdf1f..20ce04ded09 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 4bdf92cc67e..5914429db9d 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/concept/black-jack/.docs/introduction.md b/exercises/concept/black-jack/.docs/introduction.md
index b79091f4ffd..207229359dd 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/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md
index fa29f0560e7..905504a63bc 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_:
diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md
index 106e30a392f..2d61cec837d 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
diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md
index edb9f0fe10e..f9be66c3fe9 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.
diff --git a/exercises/concept/inventory-management/.docs/instructions.md b/exercises/concept/inventory-management/.docs/instructions.md
index e1c1806c6ea..718b91d3e0a 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 1b4a15dc286..2b9ef011e1b 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/little-sisters-essay/.docs/introduction.md b/exercises/concept/little-sisters-essay/.docs/introduction.md
index 1b0e9531123..24358f2fc28 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.'
```
diff --git a/exercises/concept/little-sisters-vocab/.docs/introduction.md b/exercises/concept/little-sisters-vocab/.docs/introduction.md
index d7ccff84e64..220b9b73ebc 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:
diff --git a/exercises/concept/locomotive-engineer/.docs/instructions.md b/exercises/concept/locomotive-engineer/.docs/instructions.md
index af914e4924f..1d915c143a1 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 e010c075768..66d9ba15810 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):
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 00000000000..9edd70abfe8
--- /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 00000000000..f5fa6f79012
--- /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 00000000000..f1c5744e69a
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/.docs/introduction.md
@@ -0,0 +1,221 @@
+# Dictionary Methods in Python
+
+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.
+
+### 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.
+
+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 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:
+
+```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 Via Views
+
+The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary.
+
+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.
+>>> 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 when 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'}
+```
+
+## 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`.
+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.
+
+[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
+[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
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 00000000000..f09d0f29537
--- /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 00000000000..4c3c52a0d95
--- /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 00000000000..0390944dbbd
--- /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 00000000000..d443c8bca54
--- /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 00000000000..2f5828d615f
--- /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)
diff --git a/exercises/concept/plane-tickets/.docs/hints.md b/exercises/concept/plane-tickets/.docs/hints.md
index 72d814b22ae..11508ee3838 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 290aad2ebb7..b22a84f2213 100644
--- a/exercises/concept/plane-tickets/.docs/instructions.md
+++ b/exercises/concept/plane-tickets/.docs/instructions.md
@@ -2,32 +2,51 @@
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.
+## 1. Generate seat letters
-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.
+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`.
-You can use this table as a guide:
+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.
-| x | 1 | 2 |
-| :----: | :----: | :----:|
-| Row | 5 | 21 |
-| Seat letter | A | D |
-| Result | 5A | 21D |
+```python
+>>> letters = generate_seat_letters(4)
+>>> next(letters)
+"A"
+>>> next(letters)
+"B"
+```
-## 1. Generate an amount of seats
+## 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`, ...
-`amount`: The amount of seats to be generated.
+Here is an example:
+
+| 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()` 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)
@@ -37,29 +56,32 @@ _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_:
+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']
+>>> 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'}
```
-## 3. Ticket codes
-
-Each ticket has a _12_ character long string code for identification.
+## 4. Ticket codes
-This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`.
+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 is _12_ characters long string code for identification.
-Implement a `generator` that yields a `ticket_number` given the following arguments:
+This code begins with the `assigned_seat` followed by the `flight_id`.
+The rest of the code is appended by `0s`.
-`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/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md
index 1b557b447f8..5ab9d6d2611 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/config.json b/exercises/concept/plane-tickets/.meta/config.json
index 016ff0cef23..2698188896e 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/design.md b/exercises/concept/plane-tickets/.meta/design.md
index 58542aab87c..96a4cc3cc91 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,29 @@ 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`
+- `classes`
## 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 +53,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 +75,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.
-
-
diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py
index fa8927d759a..9a3d7024076 100644
--- a/exercises/concept/plane-tickets/.meta/exemplar.py
+++ b/exercises/concept/plane-tickets/.meta/exemplar.py
@@ -1,15 +1,32 @@
-"""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: int - amount of seat letters to be generated.
+ :return: generator - generator that yields seat letters.
+
+ Seat letters are generated from A to D.
+ After D it should start again with A.
+
+ 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)
- :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
@@ -21,19 +38,17 @@ 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 passengers.
- """Assign seats to passenger.
-
- :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"}
@@ -46,12 +61,11 @@ 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])
- :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 44d685535b6..4399791b223 100644
--- a/exercises/concept/plane-tickets/plane_tickets.py
+++ b/exercises/concept/plane-tickets/plane_tickets.py
@@ -1,11 +1,26 @@
-"""Plane Tickets Exercise"""
+"""Functions to automate Conda airlines ticketing system."""
-def generate_seats(amount):
+def generate_seat_letters(amount):
+ """ 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 from A to D.
+ After D it should start again with A.
+
+ Example: A, B, C, D
+
+ """
+ pass
+
+
+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
@@ -19,11 +34,10 @@ 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])
- :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"}
@@ -32,11 +46,10 @@ 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])
- :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_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py
index f4255c43af6..1372e5cd2d7 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'],
diff --git a/exercises/practice/accumulate/.docs/instructions.md b/exercises/practice/accumulate/.docs/instructions.md
index 435e0b32469..c25a03fab18 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/accumulate/.meta/tests.toml b/exercises/practice/accumulate/.meta/tests.toml
new file mode 100644
index 00000000000..150ec2e896e
--- /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/acronym/.meta/template.j2 b/exercises/practice/acronym/.meta/template.j2
index 1373d724d31..3480ade6319 100644
--- a/exercises/practice/acronym/.meta/template.j2
+++ b/exercises/practice/acronym/.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,10 +11,9 @@
"{{ case["expected"] }}"
)
{%- endmacro %}
-{{ macros.header()}}
+
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 c5fbbfa39eb..984deef60d2 100644
--- a/exercises/practice/acronym/acronym_test.py
+++ b/exercises/practice/acronym/acronym_test.py
@@ -1,11 +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-20
+
import unittest
from acronym import (
abbreviate,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class AcronymTest(unittest.TestCase):
def test_basic(self):
@@ -39,7 +41,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/affine-cipher/.meta/template.j2 b/exercises/practice/affine-cipher/.meta/template.j2
index 2ccc2c7f863..2e92e2b4e59 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 fb9cd4a1081..f6d7c106c33 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-20
+
import unittest
from affine_cipher import (
@@ -5,8 +9,6 @@
encode,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class AffineCipherTest(unittest.TestCase):
def test_encode_yes(self):
diff --git a/exercises/practice/all-your-base/.meta/template.j2 b/exercises/practice/all-your-base/.meta/template.j2
index 20684b546c3..682f9d9e996 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,11 +25,8 @@
{%- endif %}
{% endmacro %}
-{{ macros.header() }}
-
class {{ exercise | camel_case }}Test(unittest.TestCase):
{% for case in cases -%}
{{ 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 04cf27dbae2..32c0b7b924a 100644
--- a/exercises/practice/all-your-base/all_your_base_test.py
+++ b/exercises/practice/all-your-base/all_your_base_test.py
@@ -1,11 +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-20
+
import unittest
from all_your_base import (
rebase,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class AllYourBaseTest(unittest.TestCase):
def test_single_bit_one_to_decimal(self):
@@ -101,11 +103,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 f39a9d54a4b..d0085deb8e0 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):
@@ -27,5 +29,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 5b8d32c085d..8e10b9d59b6 100644
--- a/exercises/practice/allergies/allergies_test.py
+++ b/exercises/practice/allergies/allergies_test.py
@@ -1,11 +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-20
+
import unittest
from allergies import (
Allergies,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class AllergiesTest(unittest.TestCase):
def test_eggs_not_allergic_to_anything(self):
@@ -183,7 +185,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 4841b41ee5a..e051fd81ac3 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):
@@ -30,4 +32,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 c1f573391bc..6279b805c59 100644
--- a/exercises/practice/alphametics/alphametics_test.py
+++ b/exercises/practice/alphametics/alphametics_test.py
@@ -1,11 +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-20
+
import unittest
from alphametics import (
solve,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class AlphameticsTest(unittest.TestCase):
def test_puzzle_with_three_letters(self):
@@ -106,7 +108,3 @@ def test_puzzle_with_ten_letters_and_199_addends(self):
"T": 9,
},
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/anagram/.meta/template.j2 b/exercises/practice/anagram/.meta/template.j2
index 1f74aef5bc0..c402f530b45 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 f06ad9fb53b..5b0e9c6c4d7 100644
--- a/exercises/practice/anagram/anagram_test.py
+++ b/exercises/practice/anagram/anagram_test.py
@@ -1,11 +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-20
+
import unittest
from anagram import (
find_anagrams,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class AnagramTest(unittest.TestCase):
def test_no_matches(self):
diff --git a/exercises/practice/armstrong-numbers/.meta/template.j2 b/exercises/practice/armstrong-numbers/.meta/template.j2
index f6ac6e765ea..8f917dfffc0 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):
@@ -9,6 +11,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 c3f6089e9c8..402476671db 100644
--- a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py
+++ b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py
@@ -1,11 +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-20
+
import unittest
from armstrong_numbers import (
is_armstrong_number,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ArmstrongNumbersTest(unittest.TestCase):
def test_zero_is_an_armstrong_number(self):
@@ -34,7 +36,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()
diff --git a/exercises/practice/atbash-cipher/.meta/template.j2 b/exercises/practice/atbash-cipher/.meta/template.j2
index d3eb3d87621..39908eb4bc7 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):
@@ -11,5 +13,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 98c1072afc7..f8103f81b9a 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-20
+
import unittest
from atbash_cipher import (
@@ -5,8 +9,6 @@
encode,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class AtbashCipherTest(unittest.TestCase):
def test_encode_yes(self):
@@ -61,7 +63,3 @@ def test_decode_with_no_spaces(self):
self.assertEqual(
decode("zmlyhgzxovrhlugvmzhgvkkrmthglmv"), "anobstacleisoftenasteppingstone"
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/bank-account/.meta/config.json b/exercises/practice/bank-account/.meta/config.json
index 8ab9aa0d888..51483ecb667 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 21248fa9257..90ddf31c23b 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
new file mode 100644
index 00000000000..3372b2f7fdc
--- /dev/null
+++ b/exercises/practice/bank-account/.meta/template.j2
@@ -0,0 +1,53 @@
+{%- 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()
+ {%- set inputs = case["input"]["operations"] -%}
+ {%- 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]["amount"] -%}
+ {%- set inputs = inputs[:-1] -%}
+ {% endif %}
+ {%- for input in inputs -%}
+ {%- set operation = input["operation"] -%}
+ {%- set value = input["amount"] -%}
+ {%- set expected = case["expected"] %}
+ {%- if operation and value %}
+ account.{{ operation }}({{ value }})
+ {%- elif not value and operation %}
+ {%- if not error_case and operation == "balance" %}
+ self.assertEqual(account.get_balance(), {{ expected }})
+ {%- else %}
+ account.{{ operation }}()
+ {%- 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 %}
+{% endmacro %}
+
+
+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 00000000000..1704a08c5ad
--- /dev/null
+++ b/exercises/practice/bank-account/.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.
+
+[983a1528-4ceb-45e5-8257-8ce01aceb5ed]
+description = "Newly opened account has zero balance"
+
+[e88d4ec3-c6bf-4752-8e59-5046c44e3ba7]
+description = "Single deposit"
+
+[3d9147d4-63f4-4844-8d2b-1fee2e9a2a0d]
+description = "Multiple deposits"
+
+[08f1af07-27ae-4b38-aa19-770bde558064]
+description = "Withdraw once"
+
+[6f6d242f-8c31-4ac6-8995-a90d42cad59f]
+description = "Withdraw twice"
+
+[45161c94-a094-4c77-9cec-998b70429bda]
+description = "Can do multiple operations sequentially"
+
+[f9facfaa-d824-486e-8381-48832c4bbffd]
+description = "Cannot check balance of closed account"
+
+[7a65ba52-e35c-4fd2-8159-bda2bde6e59c]
+description = "Cannot deposit into closed account"
+
+[a0a1835d-faae-4ad4-a6f3-1fcc2121380b]
+description = "Cannot deposit into unopened account"
+
+[570dfaa5-0532-4c1f-a7d3-0f65c3265608]
+description = "Cannot withdraw from closed account"
+
+[c396d233-1c49-4272-98dc-7f502dbb9470]
+description = "Cannot close an account that was not opened"
+
+[c06f534f-bdc2-4a02-a388-1063400684de]
+description = "Cannot open an already opened account"
+
+[0722d404-6116-4f92-ba3b-da7f88f1669c]
+description = "Reopened account does not retain balance"
+
+[ec42245f-9361-4341-8231-a22e8d19c52f]
+description = "Cannot withdraw more than deposited"
+
+[4f381ef8-10ef-4507-8e1d-0631ecc8ee72]
+description = "Cannot withdraw negative"
+
+[d45df9ea-1db0-47f3-b18c-d365db49d938]
+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 3a36b3ccb42..c6e6b87020d 100644
--- a/exercises/practice/bank-account/bank_account_test.py
+++ b/exercises/practice/bank-account/bank_account_test.py
@@ -1,9 +1,12 @@
-import sys
-import threading
-import time
+# 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-20
+
import unittest
-from bank_account import BankAccount
+from bank_account import (
+ BankAccount,
+)
class BankAccountTest(unittest.TestCase):
@@ -12,76 +15,86 @@ def test_newly_opened_account_has_zero_balance(self):
account.open()
self.assertEqual(account.get_balance(), 0)
- def test_can_deposit_money(self):
+ def test_single_deposit(self):
account = BankAccount()
account.open()
account.deposit(100)
self.assertEqual(account.get_balance(), 100)
- def test_can_deposit_money_sequentially(self):
+ def test_multiple_deposits(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_withdraw_once(self):
account = BankAccount()
account.open()
account.deposit(100)
- account.withdraw(50)
-
- self.assertEqual(account.get_balance(), 50)
+ account.withdraw(75)
+ self.assertEqual(account.get_balance(), 25)
- def test_can_withdraw_money_sequentially(self):
+ def test_withdraw_twice(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_do_multiple_operations_sequentially(self):
account = BankAccount()
account.open()
- account.close()
+ 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.get_balance()
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "account not open")
- def test_deposit_into_closed_account(self):
+ def test_cannot_deposit_into_closed_account(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_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_withdraw_from_closed_account(self):
+ def test_cannot_withdraw_from_closed_account(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_cannot_close_an_account_that_was_not_opened(self):
account = BankAccount()
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_cannot_open_an_already_opened_account(self):
account = BankAccount()
account.open()
with self.assertRaises(ValueError) as err:
@@ -101,7 +114,6 @@ def test_cannot_withdraw_more_than_deposited(self):
account = BankAccount()
account.open()
account.deposit(25)
-
with self.assertRaises(ValueError) as err:
account.withdraw(50)
self.assertEqual(type(err.exception), ValueError)
@@ -111,7 +123,6 @@ def test_cannot_withdraw_negative(self):
account = BankAccount()
account.open()
account.deposit(100)
-
with self.assertRaises(ValueError) as err:
account.withdraw(-50)
self.assertEqual(type(err.exception), ValueError)
@@ -120,40 +131,7 @@ def test_cannot_withdraw_negative(self):
def test_cannot_deposit_negative(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/beer-song/.meta/template.j2 b/exercises/practice/beer-song/.meta/template.j2
index 37e45402da9..1a2d6022879 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):
@@ -18,6 +20,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 4952a374c89..9232ca1c9bc 100644
--- a/exercises/practice/beer-song/beer_song_test.py
+++ b/exercises/practice/beer-song/beer_song_test.py
@@ -1,11 +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-20
+
import unittest
from beer_song import (
recite,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class BeerSongTest(unittest.TestCase):
def test_first_generic_verse(self):
@@ -369,7 +371,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 518d5ceda03..37177a163c6 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 %}
@@ -63,6 +64,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 23c1fea73b4..f44fe2d5f92 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-20
+
import unittest
from binary_search_tree import (
@@ -5,8 +9,6 @@
TreeNode,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class BinarySearchTreeTest(unittest.TestCase):
def test_data_is_retained(self):
@@ -82,7 +84,3 @@ def compare_tree(self, tree_one, tree_two):
pass
else:
raise AssertionError
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md
index 175c4c4ba8f..aa1946cfb00 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 66c4b8a45cb..03496599e75 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)
diff --git a/exercises/practice/binary-search/.meta/template.j2 b/exercises/practice/binary-search/.meta/template.j2
index f29df253194..0856646f6a9 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 9f883d17678..a47a87f9654 100644
--- a/exercises/practice/binary-search/binary_search_test.py
+++ b/exercises/practice/binary-search/binary_search_test.py
@@ -1,11 +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-20
+
import unittest
from binary_search import (
find,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class BinarySearchTest(unittest.TestCase):
def test_finds_a_value_in_an_array_with_one_element(self):
diff --git a/exercises/practice/binary/.docs/instructions.md b/exercises/practice/binary/.docs/instructions.md
index 3cfde215fc9..046fd3e0998 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/binary/.meta/config.json b/exercises/practice/binary/.meta/config.json
index 7170a4ab191..b9f5b07508a 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/bob/.meta/template.j2 b/exercises/practice/bob/.meta/template.j2
index ce22d1849cf..912f7e31bb0 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):
@@ -9,5 +12,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 40081b045f1..faba5f9612e 100644
--- a/exercises/practice/bob/bob_test.py
+++ b/exercises/practice/bob/bob_test.py
@@ -1,11 +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-20
+
import unittest
from bob import (
response,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class BobTest(unittest.TestCase):
def test_stating_something(self):
@@ -98,7 +100,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/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md
index 341ad01fbd2..906eb587619 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/book-store/.meta/template.j2 b/exercises/practice/book-store/.meta/template.j2
index 6d3a79e8ff3..9e17ab02ee5 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 %}
@@ -20,5 +23,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 50f38bc5878..87b0051faa2 100644
--- a/exercises/practice/book-store/book_store_test.py
+++ b/exercises/practice/book-store/book_store_test.py
@@ -1,11 +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-20
+
import unittest
from book_store import (
total,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class BookStoreTest(unittest.TestCase):
def test_only_a_single_book(self):
@@ -95,7 +97,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()
diff --git a/exercises/practice/bottle-song/.meta/template.j2 b/exercises/practice/bottle-song/.meta/template.j2
index 37e45402da9..1a2d6022879 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):
@@ -18,6 +20,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 89877dc4947..998721d4bfe 100644
--- a/exercises/practice/bottle-song/bottle_song_test.py
+++ b/exercises/practice/bottle-song/bottle_song_test.py
@@ -1,11 +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-20
+
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):
@@ -130,7 +132,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/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md
index 48a2fedcf42..ddce7ee4895 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/bowling/.meta/template.j2 b/exercises/practice/bowling/.meta/template.j2
index 8234dd1c5a4..2761a8de840 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 89d0a45f0bc..45d6b874835 100644
--- a/exercises/practice/bowling/bowling_test.py
+++ b/exercises/practice/bowling/bowling_test.py
@@ -1,11 +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-21
+
import unittest
from bowling import (
BowlingGame,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class BowlingTest(unittest.TestCase):
def roll_new_game(self, rolls):
@@ -209,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 72e4382dcb8..846a9652856 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 6b335174ef3..f8699e2f6db 100644
--- a/exercises/practice/change/change_test.py
+++ b/exercises/practice/change/change_test.py
@@ -1,11 +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-20
+
import unittest
from change import (
find_fewest_coins,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ChangeTest(unittest.TestCase):
def test_change_for_1_cent(self):
diff --git a/exercises/practice/circular-buffer/.meta/example.py b/exercises/practice/circular-buffer/.meta/example.py
index 8438b9839fc..538cc7bc5e2 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/circular-buffer/.meta/template.j2 b/exercises/practice/circular-buffer/.meta/template.j2
index 259d953949b..a7aea897f64 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 403ee1b5ff6..eb0663cf503 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-20
+
import unittest
from circular_buffer import (
@@ -6,8 +10,6 @@
BufferFullException,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class CircularBufferTest(unittest.TestCase):
def test_reading_empty_buffer_should_fail(self):
diff --git a/exercises/practice/clock/.meta/template.j2 b/exercises/practice/clock/.meta/template.j2
index f786b2f1c88..872dce0a42b 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 %}
@@ -29,4 +32,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 fe06336676b..6fde8e83e7e 100644
--- a/exercises/practice/clock/clock_test.py
+++ b/exercises/practice/clock/clock_test.py
@@ -1,11 +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-20
+
import unittest
from clock import (
Clock,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ClockTest(unittest.TestCase):
# Create A String Representation
@@ -177,7 +179,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/collatz-conjecture/.meta/template.j2 b/exercises/practice/collatz-conjecture/.meta/template.j2
index 4dcbae8550b..988f4a4ed98 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 b9c6df93c82..306e3db7e7c 100644
--- a/exercises/practice/collatz-conjecture/collatz_conjecture_test.py
+++ b/exercises/practice/collatz-conjecture/collatz_conjecture_test.py
@@ -1,11 +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-20
+
import unittest
from collatz_conjecture import (
steps,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class CollatzConjectureTest(unittest.TestCase):
def test_zero_steps_for_one(self):
diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2
index 86b0d71d849..7c3fb7d2dd8 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 2eca7849697..3ebdcfe1082 100644
--- a/exercises/practice/complex-numbers/complex_numbers_test.py
+++ b/exercises/practice/complex-numbers/complex_numbers_test.py
@@ -1,13 +1,14 @@
-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-19
+import math
import unittest
from complex_numbers import (
ComplexNumber,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ComplexNumbersTest(unittest.TestCase):
@@ -189,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 17c786c8652..e7303d35131 100644
--- a/exercises/practice/connect/connect_test.py
+++ b/exercises/practice/connect/connect_test.py
@@ -1,11 +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-19
+
import unittest
from connect import (
ConnectGame,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ConnectTest(unittest.TestCase):
def test_an_empty_board_has_no_winner(self):
@@ -108,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 5568ca09132..f968f0c7477 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):
@@ -9,5 +11,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 3715400ca3d..97630a67501 100644
--- a/exercises/practice/crypto-square/crypto_square_test.py
+++ b/exercises/practice/crypto-square/crypto_square_test.py
@@ -1,11 +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-19
+
import unittest
from crypto_square import (
cipher_text,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class CryptoSquareTest(unittest.TestCase):
def test_empty_plaintext_results_in_an_empty_ciphertext(self):
@@ -51,7 +53,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 13412265944..e7a4ee7d379 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]) }}
@@ -44,5 +46,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 e5e3ffd3bc0..9ee2bd19679 100644
--- a/exercises/practice/custom-set/custom_set_test.py
+++ b/exercises/practice/custom-set/custom_set_test.py
@@ -1,11 +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-19
+
import unittest
from custom_set import (
CustomSet,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class CustomSetTest(unittest.TestCase):
def test_sets_with_no_elements_are_empty(self):
@@ -212,7 +214,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/.docs/instructions.md b/exercises/practice/darts/.docs/instructions.md
index 7af7428a06c..70f0e53da7c 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/darts/.meta/template.j2 b/exercises/practice/darts/.meta/template.j2
index 09c0361b2fc..e08c6d83c53 100644
--- a/exercises/practice/darts/.meta/template.j2
+++ b/exercises/practice/darts/.meta/template.j2
@@ -1,10 +1,10 @@
{%- 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 -%}
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 e830199c9c1..5e167046177 100644
--- a/exercises/practice/darts/darts_test.py
+++ b/exercises/practice/darts/darts_test.py
@@ -1,11 +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-19
+
import unittest
from darts import (
score,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class DartsTest(unittest.TestCase):
def test_missed_target(self):
@@ -46,7 +48,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()
diff --git a/exercises/practice/diamond/.meta/template.j2 b/exercises/practice/diamond/.meta/template.j2
index e9357cc7ac1..0175388cc32 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 %}
@@ -9,5 +11,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 f0e60c862d4..6a3a2295098 100644
--- a/exercises/practice/diamond/diamond_test.py
+++ b/exercises/practice/diamond/diamond_test.py
@@ -1,11 +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-19
+
import unittest
from diamond import (
rows,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class DiamondTest(unittest.TestCase):
def test_degenerate_case_with_a_single_a_row(self):
@@ -87,7 +89,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 62b8d8d2187..db8cc69e9ca 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 %}
@@ -11,5 +13,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 8c2069e4d9a..aa7271907ac 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-19
+
import unittest
from difference_of_squares import (
@@ -6,8 +10,6 @@
sum_of_squares,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class DifferenceOfSquaresTest(unittest.TestCase):
def test_square_of_sum_1(self):
@@ -36,7 +38,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/diffie-hellman/.meta/template.j2 b/exercises/practice/diffie-hellman/.meta/template.j2
index b9634cdc302..378c28423d3 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 716b7ba1917..e24c4e742a1 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-19
+
import unittest
from diffie_hellman import (
@@ -6,8 +10,6 @@
secret,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class DiffieHellmanTest(unittest.TestCase):
def test_private_key_is_greater_than_1_and_less_than_p(self):
diff --git a/exercises/practice/dnd-character/.meta/template.j2 b/exercises/practice/dnd-character/.meta/template.j2
index adb15650537..793f3d921d9 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 3f370bc4bda..e506fdb1171 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-19
+
import unittest
from dnd_character import (
@@ -5,8 +9,6 @@
modifier,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class DndCharacterTest(unittest.TestCase):
def test_ability_modifier_for_score_3_is_n4(self):
diff --git a/exercises/practice/dominoes/.meta/template.j2 b/exercises/practice/dominoes/.meta/template.j2
index e75094e9900..f1f226b3c59 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) -%}
@@ -64,6 +66,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 a62f130cc20..4407036af12 100644
--- a/exercises/practice/dominoes/dominoes_test.py
+++ b/exercises/practice/dominoes/dominoes_test.py
@@ -1,11 +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-19
+
import unittest
from dominoes import (
can_chain,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class DominoesTest(unittest.TestCase):
def test_empty_input_empty_output(self):
@@ -128,7 +130,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/error-handling/.docs/instructions.md b/exercises/practice/error-handling/.docs/instructions.md
index 7bc1a0856ba..25dd4d2928f 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/etl/.docs/instructions.md b/exercises/practice/etl/.docs/instructions.md
index fffe64f2015..802863b5405 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 00000000000..5be65147d7f
--- /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.
diff --git a/exercises/practice/etl/.meta/config.json b/exercises/practice/etl/.meta/config.json
index 4d99e6314da..32aab8ba3ba 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/etl/.meta/template.j2 b/exercises/practice/etl/.meta/template.j2
index 8e616d8874a..0fc3ccb39fd 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,12 +13,8 @@
{{ case["property"] | to_snake }}(legacy_data), data
)
{%- endmacro %}
-{{ macros.header()}}
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 693ecb9e867..d6eed70a574 100644
--- a/exercises/practice/etl/etl_test.py
+++ b/exercises/practice/etl/etl_test.py
@@ -1,11 +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-19
+
import unittest
from etl import (
transform,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class EtlTest(unittest.TestCase):
def test_single_letter(self):
@@ -62,7 +64,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 d1d93ad7113..0a9a631fc77 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 -%}
@@ -9,5 +11,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 1552f0edb6a..cecb3c5633f 100644
--- a/exercises/practice/flatten-array/flatten_array_test.py
+++ b/exercises/practice/flatten-array/flatten_array_test.py
@@ -1,11 +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-19
+
import unittest
from flatten_array import (
flatten,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class FlattenArrayTest(unittest.TestCase):
def test_empty(self):
@@ -66,7 +68,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()
diff --git a/exercises/practice/food-chain/.meta/template.j2 b/exercises/practice/food-chain/.meta/template.j2
index e1851f7a30e..bc5c61d5712 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 %}
@@ -10,5 +12,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 d77953527e8..0cd42356ab1 100644
--- a/exercises/practice/food-chain/food_chain_test.py
+++ b/exercises/practice/food-chain/food_chain_test.py
@@ -1,11 +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-19
+
import unittest
from food_chain import (
recite,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class FoodChainTest(unittest.TestCase):
def test_fly(self):
@@ -180,7 +182,3 @@ def test_full_song(self):
"She's dead, of course!",
],
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/forth/.meta/template.j2 b/exercises/practice/forth/.meta/template.j2
index b6fe07e71a0..0b5dd2b89ef 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 f565c6455e3..f8402bdd64c 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-19
+
import unittest
from forth import (
@@ -5,8 +9,6 @@
StackUnderflowError,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ForthTest(unittest.TestCase):
def test_parsing_and_numbers_numbers_just_get_pushed_onto_the_stack(self):
diff --git a/exercises/practice/gigasecond/.docs/hints.md b/exercises/practice/gigasecond/.docs/hints.md
index 0375a753afe..30a36d1be94 100644
--- a/exercises/practice/gigasecond/.docs/hints.md
+++ b/exercises/practice/gigasecond/.docs/hints.md
@@ -1,6 +1,17 @@
# 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][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.
+
+- 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].
+
+[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.
diff --git a/exercises/practice/gigasecond/.docs/instructions.append.md b/exercises/practice/gigasecond/.docs/instructions.append.md
index 832fec734c8..ccb6f2d05bb 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/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md
index 74afaa994f3..18a3dc20058 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/gigasecond/.meta/config.json b/exercises/practice/gigasecond/.meta/config.json
index 59b2d30cc9e..76af3dac048 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": [
diff --git a/exercises/practice/gigasecond/.meta/template.j2 b/exercises/practice/gigasecond/.meta/template.j2
index 31d032a46fd..e40462e2d8b 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() }}
@@ -9,6 +11,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 76ff16a64c9..dd648e14bd6 100644
--- a/exercises/practice/gigasecond/gigasecond_test.py
+++ b/exercises/practice/gigasecond/gigasecond_test.py
@@ -1,3 +1,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-19
+
from datetime import datetime
import unittest
@@ -5,8 +9,6 @@
add,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class GigasecondTest(unittest.TestCase):
def test_date_only_specification_of_time(self):
@@ -33,7 +35,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/go-counting/go_counting_test.py b/exercises/practice/go-counting/go_counting_test.py
index 30972ca6a20..aec80a1b369 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-19
+
import unittest
from go_counting import (
@@ -7,8 +11,6 @@
NONE,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class GoCountingTest(unittest.TestCase):
def test_black_corner_territory_on_5x5_board(self):
@@ -83,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 8a86c1a8df7..a97fa010db0 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 4672efe88ee..30d91c6c57d 100644
--- a/exercises/practice/grade-school/grade_school_test.py
+++ b/exercises/practice/grade-school/grade_school_test.py
@@ -1,11 +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-19
+
import unittest
from grade_school import (
School,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class GradeSchoolTest(unittest.TestCase):
def test_roster_is_empty_when_no_student_is_added(self):
diff --git a/exercises/practice/grains/.meta/template.j2 b/exercises/practice/grains/.meta/template.j2
index 5ea2b5b0f8d..18180801d68 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 f7e277c0702..8d729fb6f8e 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-19
+
import unittest
from grains import (
@@ -5,8 +9,6 @@
total,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class GrainsTest(unittest.TestCase):
def test_grains_on_square_1(self):
diff --git a/exercises/practice/grep/.meta/template.j2 b/exercises/practice/grep/.meta/template.j2
index 6091fd667e5..fdb09b6dbf6 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") -%}
@@ -48,5 +50,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 804071542d0..81952884566 100644
--- a/exercises/practice/grep/grep_test.py
+++ b/exercises/practice/grep/grep_test.py
@@ -1,11 +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-19
+
+import io
import unittest
from grep import (
grep,
)
-
-# Tests adapted from `problem-specifications//canonical-data.json`
-import io
from unittest import mock
FILE_TEXT = {
@@ -290,7 +292,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/hamming/.meta/template.j2 b/exercises/practice/hamming/.meta/template.j2
index 498fbc56ce6..23efd5b73e2 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 06a08b09dd8..df58ef002d8 100644
--- a/exercises/practice/hamming/hamming_test.py
+++ b/exercises/practice/hamming/hamming_test.py
@@ -1,11 +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-19
+
import unittest
from hamming import (
distance,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class HammingTest(unittest.TestCase):
def test_empty_strands(self):
diff --git a/exercises/practice/hello-world/.meta/template.j2 b/exercises/practice/hello-world/.meta/template.j2
index dbcde585e9c..600120d665f 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 245f5434fa6..15473f5557b 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/hexadecimal/.docs/instructions.md b/exercises/practice/hexadecimal/.docs/instructions.md
index 3e5afa77c6e..a3c648e32f7 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/hexadecimal/.meta/config.json b/exercises/practice/hexadecimal/.meta/config.json
index 352ca16d78c..b61e1a2c057 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/high-scores/.meta/template.j2 b/exercises/practice/high-scores/.meta/template.j2
index c32ae121aee..36e218c5f47 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 f1406dd852d..6a926e32fde 100644
--- a/exercises/practice/high-scores/high_scores_test.py
+++ b/exercises/practice/high-scores/high_scores_test.py
@@ -1,11 +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-19
+
import unittest
from high_scores import (
HighScores,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class HighScoresTest(unittest.TestCase):
def test_list_of_scores(self):
diff --git a/exercises/practice/house/.meta/template.j2 b/exercises/practice/house/.meta/template.j2
index 9683c07a993..6e61bc88ecc 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):
@@ -10,5 +12,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 0facef614fe..d859fb4f5ea 100644
--- a/exercises/practice/house/house_test.py
+++ b/exercises/practice/house/house_test.py
@@ -1,11 +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-19
+
import unittest
from house import (
recite,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class HouseTest(unittest.TestCase):
def test_verse_one_the_house_that_jack_built(self):
@@ -126,7 +128,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 ce973c491d4..eae9dbc1148 100644
--- a/exercises/practice/isbn-verifier/.meta/template.j2
+++ b/exercises/practice/isbn-verifier/.meta/template.j2
@@ -1,11 +1,10 @@
{%- 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 -%}
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 f2f97770d7e..dbcddf19d48 100644
--- a/exercises/practice/isbn-verifier/isbn_verifier_test.py
+++ b/exercises/practice/isbn-verifier/isbn_verifier_test.py
@@ -1,11 +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-19
+
import unittest
from isbn_verifier import (
is_valid,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class IsbnVerifierTest(unittest.TestCase):
def test_valid_isbn(self):
@@ -64,7 +66,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()
diff --git a/exercises/practice/isogram/.meta/template.j2 b/exercises/practice/isogram/.meta/template.j2
index fbec4a93a84..d75c2f49044 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 8d00d2b5f52..c65984f6e64 100644
--- a/exercises/practice/isogram/isogram_test.py
+++ b/exercises/practice/isogram/isogram_test.py
@@ -1,11 +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-19
+
import unittest
from isogram import (
is_isogram,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class IsogramTest(unittest.TestCase):
def test_empty_string(self):
diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md
index 93469a09cc0..2347ab262ca 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/killer-sudoku-helper/.meta/template.j2 b/exercises/practice/killer-sudoku-helper/.meta/template.j2
index c107933784f..43fca4aa994 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 b5732054653..2479bfc8958 100644
--- a/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py
+++ b/exercises/practice/killer-sudoku-helper/killer_sudoku_helper_test.py
@@ -1,11 +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-19
+
import unittest
from killer_sudoku_helper import (
combinations,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class KillerSudokuHelperTest(unittest.TestCase):
def test_1(self):
diff --git a/exercises/practice/kindergarten-garden/.meta/template.j2 b/exercises/practice/kindergarten-garden/.meta/template.j2
index f1dc54254de..4ba100bbe6e 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 95095cabed8..fd4d0222388 100644
--- a/exercises/practice/kindergarten-garden/kindergarten_garden_test.py
+++ b/exercises/practice/kindergarten-garden/kindergarten_garden_test.py
@@ -1,11 +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-19
+
import unittest
from kindergarten_garden import (
Garden,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class KindergartenGardenTest(unittest.TestCase):
def test_partial_garden_garden_with_single_student(self):
diff --git a/exercises/practice/knapsack/.meta/template.j2 b/exercises/practice/knapsack/.meta/template.j2
index 12760c7abae..e4282e9ce30 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 -%}
@@ -13,5 +15,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 0a792f179c3..011f7ba8324 100644
--- a/exercises/practice/knapsack/knapsack_test.py
+++ b/exercises/practice/knapsack/knapsack_test.py
@@ -1,11 +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-19
+
import unittest
from knapsack import (
maximum_value,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class KnapsackTest(unittest.TestCase):
def test_no_items(self):
@@ -100,7 +102,3 @@ def test_15_items(self):
),
1458,
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/largest-series-product/.docs/instructions.append.md b/exercises/practice/largest-series-product/.docs/instructions.append.md
index 9250db2e8ca..5a0f9b92064 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")
diff --git a/exercises/practice/largest-series-product/.docs/instructions.md b/exercises/practice/largest-series-product/.docs/instructions.md
index 08586dd5933..f297b57f7c4 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 00000000000..597bb5fa15d
--- /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.
diff --git a/exercises/practice/largest-series-product/.meta/template.j2 b/exercises/practice/largest-series-product/.meta/template.j2
index 7da257f4c2e..07fe947fb56 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/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml
index 6c111adf0f1..88316925977 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 0a67d4ca14e..e5056236736 100644
--- a/exercises/practice/largest-series-product/largest_series_product_test.py
+++ b/exercises/practice/largest-series-product/largest_series_product_test.py
@@ -1,11 +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-19
+
import unittest
from largest_series_product import (
largest_product,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class LargestSeriesProductTest(unittest.TestCase):
def test_finds_the_largest_product_if_span_equals_length(self):
@@ -46,12 +48,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)
diff --git a/exercises/practice/leap/.meta/template.j2 b/exercises/practice/leap/.meta/template.j2
index 968306fcb71..d3607dcd3e3 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 -%}
@@ -11,5 +13,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 e51aeb423a7..6a1d732c987 100644
--- a/exercises/practice/leap/leap_test.py
+++ b/exercises/practice/leap/leap_test.py
@@ -1,11 +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-19
+
import unittest
from leap import (
leap_year,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class LeapTest(unittest.TestCase):
def test_year_not_divisible_by_4_in_common_year(self):
@@ -34,7 +36,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/ledger/.meta/config.json b/exercises/practice/ledger/.meta/config.json
index eeb5e2ccdf1..5b9998ba1b3 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 00000000000..a3dbcf26f70
--- /dev/null
+++ b/exercises/practice/ledger/.meta/template.j2
@@ -0,0 +1,24 @@
+{%- 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):
+ 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 00000000000..e71dfbfcafd
--- /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 493109b847b..c0dcf94f738 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 f0de049df19..cc37167146d 100644
--- a/exercises/practice/ledger/ledger_test.py
+++ b/exercises/practice/ledger/ledger_test.py
@@ -1,147 +1,173 @@
-# -*- coding: utf-8 -*-
+# 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
+
import unittest
-from ledger import format_entries, create_entry
+from ledger import (
+ format_entries,
+ create_entry,
+)
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()
diff --git a/exercises/practice/linked-list/.docs/instructions.md b/exercises/practice/linked-list/.docs/instructions.md
index 3d949d39357..edf4055b38c 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 00000000000..6e83ae7b6e5
--- /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.
diff --git a/exercises/practice/linked-list/.meta/template.j2 b/exercises/practice/linked-list/.meta/template.j2
index a753efc3df4..604c5f5163c 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 3af5bc221e4..6724a1ebcef 100644
--- a/exercises/practice/linked-list/linked_list_test.py
+++ b/exercises/practice/linked-list/linked_list_test.py
@@ -1,11 +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-19
+
import unittest
from linked_list import (
LinkedList,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class LinkedListTest(unittest.TestCase):
def test_pop_gets_element_from_the_list(self):
diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md
index d34533387ae..ccfc2f8b2ac 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/list-ops/.meta/additional_tests.json b/exercises/practice/list-ops/.meta/additional_tests.json
index 7eb1d65c767..39c0a8bfa63 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/config.json b/exercises/practice/list-ops/.meta/config.json
index efc025a3543..cdb33138a4e 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",
diff --git a/exercises/practice/list-ops/.meta/example.py b/exercises/practice/list-ops/.meta/example.py
index ee43248f99e..75f45033e2b 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 c346d3c20fb..2e0a1bdd639 100644
--- a/exercises/practice/list-ops/.meta/template.j2
+++ b/exercises/practice/list-ops/.meta/template.j2
@@ -1,8 +1,11 @@
{%- 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", "%") %}
- {% set function = function.replace("/", "//") %}
lambda {{function}}
{%- endmacro %}
@@ -37,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 -%}
@@ -53,6 +55,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
{{ test_case(case) }}
{% endfor %}
{%- endif %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml
index 7fe3c8d1a9c..08b1edc0443 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 cff6156dc65..ea0d2136599 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-19
+
import unittest
from list_ops import (
@@ -11,8 +15,6 @@
map as list_ops_map,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ListOpsTest(unittest.TestCase):
def test_append_empty_lists(self):
@@ -63,12 +65,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,13 +92,11 @@ 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!",
)
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/luhn/.meta/template.j2 b/exercises/practice/luhn/.meta/template.j2
index af8489b3c89..568d26efc61 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 bbc1f8eb1f8..58234eb7d55 100644
--- a/exercises/practice/luhn/luhn_test.py
+++ b/exercises/practice/luhn/luhn_test.py
@@ -1,11 +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-19
+
import unittest
from luhn import (
Luhn,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class LuhnTest(unittest.TestCase):
def test_single_digit_strings_can_not_be_valid(self):
diff --git a/exercises/practice/markdown/.meta/template.j2 b/exercises/practice/markdown/.meta/template.j2
index da2a037cec8..e0b5f717c7b 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 5c059c725a8..ad6d243a635 100644
--- a/exercises/practice/markdown/markdown_test.py
+++ b/exercises/practice/markdown/markdown_test.py
@@ -1,11 +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-19
+
import unittest
from markdown import (
parse,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class MarkdownTest(unittest.TestCase):
def test_parses_normal_text_as_a_paragraph(self):
diff --git a/exercises/practice/matching-brackets/.meta/template.j2 b/exercises/practice/matching-brackets/.meta/template.j2
index bc8f0830264..53abdc8efbe 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 -%}
@@ -8,7 +10,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 fd23bd78403..a8321d94ad1 100644
--- a/exercises/practice/matching-brackets/matching_brackets_test.py
+++ b/exercises/practice/matching-brackets/matching_brackets_test.py
@@ -1,11 +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-19
+
import unittest
from matching_brackets import (
is_paired,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class MatchingBracketsTest(unittest.TestCase):
def test_paired_square_brackets(self):
@@ -72,7 +74,3 @@ def test_complex_latex_expression(self):
),
True,
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/matrix/.meta/template.j2 b/exercises/practice/matrix/.meta/template.j2
index ae1c08c3b68..090f7d85c88 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 10820ab6c12..6f5ce7da5fe 100644
--- a/exercises/practice/matrix/matrix_test.py
+++ b/exercises/practice/matrix/matrix_test.py
@@ -1,11 +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-19
+
import unittest
from matrix import (
Matrix,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class MatrixTest(unittest.TestCase):
def test_extract_row_from_one_number_matrix(self):
@@ -39,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 39d9cf8db84..8078c5a3a34 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 9ecc3d8a1df..ec9e22b556d 100644
--- a/exercises/practice/meetup/meetup_test.py
+++ b/exercises/practice/meetup/meetup_test.py
@@ -1,3 +1,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-19
+
from datetime import date
import unittest
@@ -6,8 +10,6 @@
MeetupDayException,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
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/.meta/template.j2 b/exercises/practice/minesweeper/.meta/template.j2
index b34be795739..68570a9fd4b 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 d5e6b78b7f4..f6ffab43609 100644
--- a/exercises/practice/minesweeper/minesweeper_test.py
+++ b/exercises/practice/minesweeper/minesweeper_test.py
@@ -1,11 +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-19
+
import unittest
from minesweeper import (
annotate,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class MinesweeperTest(unittest.TestCase):
def test_no_rows(self):
diff --git a/exercises/practice/nth-prime/.meta/template.j2 b/exercises/practice/nth-prime/.meta/template.j2
index 453ade58894..ab3d42adff7 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 2a3e1b3f2e8..6f30f30aa0f 100644
--- a/exercises/practice/nth-prime/nth_prime_test.py
+++ b/exercises/practice/nth-prime/nth_prime_test.py
@@ -1,11 +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-19
+
import unittest
from nth_prime import (
prime,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
def prime_range(n):
"""Returns a list of the first n primes"""
diff --git a/exercises/practice/nucleotide-count/.docs/instructions.md b/exercises/practice/nucleotide-count/.docs/instructions.md
index 57667134b72..548d9ba5a5e 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/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json
index 4e91bb7ee9f..e0c108f7b88 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/ocr-numbers/.meta/template.j2 b/exercises/practice/ocr-numbers/.meta/template.j2
index c829331802b..94286b1ef8b 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 eeafb2fb314..3d24adc557c 100644
--- a/exercises/practice/ocr-numbers/ocr_numbers_test.py
+++ b/exercises/practice/ocr-numbers/ocr_numbers_test.py
@@ -1,11 +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-19
+
import unittest
from ocr_numbers import (
convert,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class OcrNumbersTest(unittest.TestCase):
def test_recognizes_0(self):
diff --git a/exercises/practice/octal/.docs/instructions.md b/exercises/practice/octal/.docs/instructions.md
index 81f108384b0..65ce135c6ff 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/octal/.meta/config.json b/exercises/practice/octal/.meta/config.json
index e39e3fb6870..4fe6c11bdef 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/paasio/paasio_test.py b/exercises/practice/paasio/paasio_test.py
index 07a5cea7a55..c5c7ff73176 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/exercises/practice/palindrome-products/.meta/template.j2 b/exercises/practice/palindrome-products/.meta/template.j2
index 634adaf53fc..f1862fc0c88 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 0fd7eaece32..e9339b5d25d 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-19
+
import unittest
from palindrome_products import (
@@ -5,8 +9,6 @@
smallest,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class PalindromeProductsTest(unittest.TestCase):
def test_find_the_smallest_palindrome_from_single_digit_factors(self):
diff --git a/exercises/practice/pangram/.approaches/bitfield/content.md b/exercises/practice/pangram/.approaches/bitfield/content.md
index 20e460fea0d..b292540567e 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 729cc967dc9..1b423744479 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
diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md
index d5698bc2a29..817c872d907 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 d38fa341dfa..32b6f1fc317 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/pangram/.meta/template.j2 b/exercises/practice/pangram/.meta/template.j2
index 7e93a443548..df38b578777 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 -#}
@@ -22,4 +25,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 1031cb4e6a4..09e303d4407 100644
--- a/exercises/practice/pangram/pangram_test.py
+++ b/exercises/practice/pangram/pangram_test.py
@@ -1,11 +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-19
+
import unittest
from pangram import (
is_pangram,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class PangramTest(unittest.TestCase):
def test_empty_sentence(self):
@@ -50,7 +52,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()
diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md
index a5b936c5e2a..85abcf86a42 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/parallel-letter-frequency/.meta/tests.toml b/exercises/practice/parallel-letter-frequency/.meta/tests.toml
new file mode 100644
index 00000000000..6cf36e6fd2d
--- /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/pascals-triangle/.meta/template.j2 b/exercises/practice/pascals-triangle/.meta/template.j2
index 0041fd53c2b..936db7587c8 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 dc7b7ce1111..21f68895522 100644
--- a/exercises/practice/pascals-triangle/pascals_triangle_test.py
+++ b/exercises/practice/pascals-triangle/pascals_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/pascals-triangle/canonical-data.json
+# File last updated on 2023-07-19
+
import sys
import unittest
@@ -5,8 +9,6 @@
rows,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
TRIANGLE = [
[1],
[1, 1],
diff --git a/exercises/practice/perfect-numbers/.meta/template.j2 b/exercises/practice/perfect-numbers/.meta/template.j2
index c92e39ca402..e6cd0310043 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 786f92630ef..eef8661cef1 100644
--- a/exercises/practice/perfect-numbers/perfect_numbers_test.py
+++ b/exercises/practice/perfect-numbers/perfect_numbers_test.py
@@ -1,11 +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-19
+
import unittest
from perfect_numbers import (
classify,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class PerfectNumbersTest(unittest.TestCase):
def test_smallest_perfect_number_is_classified_correctly(self):
diff --git a/exercises/practice/phone-number/.meta/template.j2 b/exercises/practice/phone-number/.meta/template.j2
index 2b2826217c1..faff6e206a9 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 72ff3b099ac..2b018dfaaf3 100644
--- a/exercises/practice/phone-number/phone_number_test.py
+++ b/exercises/practice/phone-number/phone_number_test.py
@@ -1,11 +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-19
+
import unittest
from phone_number import (
PhoneNumber,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class PhoneNumberTest(unittest.TestCase):
def test_cleans_the_number(self):
diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md
index c9de5ca1865..032905aa9b0 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/pig-latin/.meta/template.j2 b/exercises/practice/pig-latin/.meta/template.j2
index f516f6f1a8c..5badcb774fd 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 %}
@@ -11,5 +13,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 cf666ddf3ad..e5a441eb6b9 100644
--- a/exercises/practice/pig-latin/pig_latin_test.py
+++ b/exercises/practice/pig-latin/pig_latin_test.py
@@ -1,11 +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-19
+
import unittest
from pig_latin import (
translate,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class PigLatinTest(unittest.TestCase):
def test_word_beginning_with_a(self):
@@ -73,7 +75,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/point-mutations/.meta/config.json b/exercises/practice/point-mutations/.meta/config.json
index 2d8d765c187..b02f3706511 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/poker/.meta/template.j2 b/exercises/practice/poker/.meta/template.j2
index 099626a1349..730d54dd69f 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):
@@ -8,5 +10,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 f436e48120b..c80aad6f059 100644
--- a/exercises/practice/poker/poker_test.py
+++ b/exercises/practice/poker/poker_test.py
@@ -1,11 +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-19
+
import unittest
from poker import (
best_hands,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class PokerTest(unittest.TestCase):
def test_single_hand_always_wins(self):
@@ -209,7 +211,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/pov/.meta/template.j2 b/exercises/practice/pov/.meta/template.j2
index aee59684a19..00d1f005a37 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 4bbdbaa8718..2436ebc2db9 100644
--- a/exercises/practice/pov/pov_test.py
+++ b/exercises/practice/pov/pov_test.py
@@ -1,11 +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-19
+
import unittest
from pov import (
Tree,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
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/.meta/template.j2 b/exercises/practice/prime-factors/.meta/template.j2
index 4178af79aac..cc9a9fe3841 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 02b36e7a16c..4f6865036e5 100644
--- a/exercises/practice/prime-factors/prime_factors_test.py
+++ b/exercises/practice/prime-factors/prime_factors_test.py
@@ -1,11 +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-19
+
import unittest
from prime_factors import (
factors,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class PrimeFactorsTest(unittest.TestCase):
def test_no_factors(self):
diff --git a/exercises/practice/protein-translation/.meta/template.j2 b/exercises/practice/protein-translation/.meta/template.j2
index 5cc931bcfd5..e0b591609f1 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 -%}
@@ -9,5 +11,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 046c0a43e76..91e6324b6e3 100644
--- a/exercises/practice/protein-translation/protein_translation_test.py
+++ b/exercises/practice/protein-translation/protein_translation_test.py
@@ -1,11 +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-19
+
import unittest
from protein_translation import (
proteins,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ProteinTranslationTest(unittest.TestCase):
def test_methionine_rna_sequence(self):
@@ -132,7 +134,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/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2
index cf50a261903..333a2d47d73 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 69905dadf4a..8c09283c020 100644
--- a/exercises/practice/proverb/proverb_test.py
+++ b/exercises/practice/proverb/proverb_test.py
@@ -1,10 +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-19
+
import unittest
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!
diff --git a/exercises/practice/pythagorean-triplet/.meta/template.j2 b/exercises/practice/pythagorean-triplet/.meta/template.j2
index b3836a54427..96fa266789d 100644
--- a/exercises/practice/pythagorean-triplet/.meta/template.j2
+++ b/exercises/practice/pythagorean-triplet/.meta/template.j2
@@ -1,15 +1,11 @@
{%- import "generator_macros.j2" as macros with context -%}
+{{ macros.canonical_ref() }}
-{{ macros.header() }}
+{{ 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 -%}
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 70d501c4fbc..91137932245 100644
--- a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py
+++ b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py
@@ -1,15 +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-19
+
import unittest
from pythagorean_triplet import (
triplets_with_sum,
)
-# 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):
@@ -53,7 +51,3 @@ def test_triplets_for_large_number(self):
[7500, 10000, 12500],
],
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md
index dce0fc29850..ad7ea954796 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/queen-attack/.meta/template.j2 b/exercises/practice/queen-attack/.meta/template.j2
index 6a08c5251e2..b8be7aeaafa 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 34c212af19e..e1289aebcef 100644
--- a/exercises/practice/queen-attack/queen_attack_test.py
+++ b/exercises/practice/queen-attack/queen_attack_test.py
@@ -1,11 +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-19
+
import unittest
from queen_attack import (
Queen,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class QueenAttackTest(unittest.TestCase):
# Test creation of Queens with valid and invalid positions
diff --git a/exercises/practice/rail-fence-cipher/.meta/template.j2 b/exercises/practice/rail-fence-cipher/.meta/template.j2
index 2c11e606c67..4df5b2c66d4 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 %}
@@ -12,5 +14,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 65743180a7c..f82066ca274 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-19
+
import unittest
from rail_fence_cipher import (
@@ -5,8 +9,6 @@
encode,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RailFenceCipherTest(unittest.TestCase):
def test_encode_with_two_rails(self):
@@ -33,7 +35,3 @@ def test_decode_with_six_rails(self):
decode("133714114238148966225439541018335470986172518171757571896261", 6),
"112358132134558914423337761098715972584418167651094617711286",
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/raindrops/.meta/template.j2 b/exercises/practice/raindrops/.meta/template.j2
index d1b0d2f8612..c84975c9bbb 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 -%}
@@ -16,5 +19,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 247f4daa47b..b07e70dc24a 100644
--- a/exercises/practice/raindrops/raindrops_test.py
+++ b/exercises/practice/raindrops/raindrops_test.py
@@ -1,11 +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-19
+
import unittest
from raindrops import (
convert,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RaindropsTest(unittest.TestCase):
def test_the_sound_for_1_is_1(self):
@@ -63,7 +65,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/rational-numbers/.meta/template.j2 b/exercises/practice/rational-numbers/.meta/template.j2
index 026bd1de48c..eb640c95757 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 e7a50e528e8..181bb128bf2 100644
--- a/exercises/practice/rational-numbers/rational_numbers_test.py
+++ b/exercises/practice/rational-numbers/rational_numbers_test.py
@@ -1,12 +1,13 @@
-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-19
+
import unittest
from rational_numbers import (
Rational,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RationalNumbersTest(unittest.TestCase):
diff --git a/exercises/practice/react/.meta/template.j2 b/exercises/practice/react/.meta/template.j2
index f9fb637c433..f46e0f39e3d 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 2ba86a80ffa..1f917e40b41 100644
--- a/exercises/practice/react/react_test.py
+++ b/exercises/practice/react/react_test.py
@@ -1,5 +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-19
+from functools import partial
import unittest
from react import (
@@ -7,8 +10,6 @@
ComputeCell,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ReactTest(unittest.TestCase):
def test_input_cells_have_a_value(self):
diff --git a/exercises/practice/rectangles/.meta/template.j2 b/exercises/practice/rectangles/.meta/template.j2
index 0d82cb8421d..0061968aa25 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 -%}
@@ -8,5 +10,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 59c622de769..89de9f28647 100644
--- a/exercises/practice/rectangles/rectangles_test.py
+++ b/exercises/practice/rectangles/rectangles_test.py
@@ -1,11 +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-19
+
import unittest
from rectangles import (
rectangles,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RectanglesTest(unittest.TestCase):
def test_no_rows(self):
@@ -98,7 +100,3 @@ def test_rectangles_must_have_four_sides(self):
),
5,
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/resistor-color-duo/.meta/template.j2 b/exercises/practice/resistor-color-duo/.meta/template.j2
index cabf271cae5..ac0a560ac0e 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 72df473f071..5a67016d894 100644
--- a/exercises/practice/resistor-color-duo/resistor_color_duo_test.py
+++ b/exercises/practice/resistor-color-duo/resistor_color_duo_test.py
@@ -1,11 +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-19
+
import unittest
from resistor_color_duo import (
value,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ResistorColorDuoTest(unittest.TestCase):
def test_brown_and_black(self):
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 00000000000..d76c567b5d0
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.docs/instructions.md
@@ -0,0 +1,79 @@
+# 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-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 |
+| ------- | ------- | ------- | ---------- | --------- |
+| Value_1 | Value_2 | Value_3 | Multiplier | Tolerance |
+
+Meaning
+
+- 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:
+
+"... 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 00000000000..fd9e05efc4d
--- /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_.
+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.
+ 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 00000000000..edf50b4a1e1
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.meta/config.json
@@ -0,0 +1,20 @@
+{
+ "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 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"
+}
diff --git a/exercises/practice/resistor-color-expert/.meta/example.py b/exercises/practice/resistor-color-expert/.meta/example.py
new file mode 100644
index 00000000000..0fd6e42fcd9
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.meta/example.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 resistor_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-expert/.meta/tests.toml b/exercises/practice/resistor-color-expert/.meta/tests.toml
new file mode 100644
index 00000000000..4f57723a19f
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.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-expert/resistor_color_expert.py b/exercises/practice/resistor-color-expert/resistor_color_expert.py
new file mode 100644
index 00000000000..f36881c5a0e
--- /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 00000000000..bcf2052c021
--- /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 ResistorColorExpertTest(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-trio/.meta/template.j2 b/exercises/practice/resistor-color-trio/.meta/template.j2
index 54814bed31d..aa458450031 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 ddfdfb6930c..05f794281d3 100644
--- a/exercises/practice/resistor-color-trio/resistor_color_trio_test.py
+++ b/exercises/practice/resistor-color-trio/resistor_color_trio_test.py
@@ -1,11 +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-19
+
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):
diff --git a/exercises/practice/resistor-color/.meta/template.j2 b/exercises/practice/resistor-color/.meta/template.j2
index e186e3562ec..0a6830e8ca2 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 -%}
@@ -14,5 +16,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 62ab48625fd..d86d7553215 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-19
+
import unittest
from resistor_color import (
@@ -5,8 +9,6 @@
colors,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ResistorColorTest(unittest.TestCase):
def test_black(self):
@@ -32,7 +34,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 9bd4e751df7..a467adb569d 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"] -%}
@@ -20,5 +22,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 8092aec2374..6d0bd213a15 100644
--- a/exercises/practice/rest-api/rest_api_test.py
+++ b/exercises/practice/rest-api/rest_api_test.py
@@ -1,12 +1,14 @@
+# 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-19
+
+import json
import unittest
from rest_api import (
RestAPI,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-import json
-
class RestApiTest(unittest.TestCase):
def test_no_users(self):
@@ -159,7 +161,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 f7e1e41f3a2..bf60c0643e8 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 -%}
@@ -11,5 +13,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 78212b41def..0c3298704cc 100644
--- a/exercises/practice/reverse-string/reverse_string_test.py
+++ b/exercises/practice/reverse-string/reverse_string_test.py
@@ -1,11 +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-19
+
import unittest
from reverse_string import (
reverse,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ReverseStringTest(unittest.TestCase):
def test_an_empty_string(self):
@@ -25,7 +27,3 @@ def test_a_palindrome(self):
def test_an_even_sized_word(self):
self.assertEqual(reverse("drawer"), "reward")
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md
index 851bdb49db7..36da381f5a7 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 00000000000..6b3f44b532d
--- /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/
+~~~~
diff --git a/exercises/practice/rna-transcription/.meta/template.j2 b/exercises/practice/rna-transcription/.meta/template.j2
index 6be1210ad61..04caf39d50f 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):
@@ -9,5 +11,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 aa062329593..766f20299e2 100644
--- a/exercises/practice/rna-transcription/rna_transcription_test.py
+++ b/exercises/practice/rna-transcription/rna_transcription_test.py
@@ -1,11 +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-19
+
import unittest
from rna_transcription import (
to_rna,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RnaTranscriptionTest(unittest.TestCase):
def test_empty_rna_sequence(self):
@@ -25,7 +27,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()
diff --git a/exercises/practice/robot-name/.approaches/config.json b/exercises/practice/robot-name/.approaches/config.json
new file mode 100644
index 00000000000..c71235ec7e4
--- /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 00000000000..9dc810ed5ea
--- /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 00000000000..6e5fad83e95
--- /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 00000000000..341230946b2
--- /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 00000000000..0aa9f9a3fab
--- /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 00000000000..e6ab23e7be5
--- /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
diff --git a/exercises/practice/robot-simulator/.meta/template.j2 b/exercises/practice/robot-simulator/.meta/template.j2
index e7297734377..787ff40bb63 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 ffb5728c301..69825c11186 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-19
+
import unittest
from robot_simulator import (
@@ -8,8 +12,6 @@
WEST,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RobotSimulatorTest(unittest.TestCase):
diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md
index bb7e909dbf2..247ea0892e0 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/roman-numerals/.meta/template.j2 b/exercises/practice/roman-numerals/.meta/template.j2
index f85f619fa70..a972569f0f3 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 59e5e697fa2..d2120f9b151 100644
--- a/exercises/practice/roman-numerals/roman_numerals_test.py
+++ b/exercises/practice/roman-numerals/roman_numerals_test.py
@@ -1,10 +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-19
+
import unittest
from roman_numerals import (
roman,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
+
class RomanNumeralsTest(unittest.TestCase):
def test_1_is_i(self):
self.assertEqual(roman(1), "I")
diff --git a/exercises/practice/rotational-cipher/.meta/template.j2 b/exercises/practice/rotational-cipher/.meta/template.j2
index 5a84641528c..94a54cf6a86 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 %}
@@ -10,5 +12,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 88b64d6edb4..ca22735ef9b 100644
--- a/exercises/practice/rotational-cipher/rotational_cipher_test.py
+++ b/exercises/practice/rotational-cipher/rotational_cipher_test.py
@@ -1,11 +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-19
+
import unittest
from rotational_cipher import (
rotate,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RotationalCipherTest(unittest.TestCase):
def test_rotate_a_by_0_same_output_as_input(self):
@@ -40,7 +42,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/run-length-encoding/.meta/template.j2 b/exercises/practice/run-length-encoding/.meta/template.j2
index 54a4a5be613..6bd93167bc3 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 9ac78f6d23c..8d65cb62e23 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-19
+
import unittest
from run_length_encoding import (
@@ -5,8 +9,6 @@
decode,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RunLengthEncodingTest(unittest.TestCase):
def test_encode_empty_string(self):
diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md
index 920ecffed98..c585568b462 100644
--- a/exercises/practice/saddle-points/.docs/instructions.md
+++ b/exercises/practice/saddle-points/.docs/instructions.md
@@ -1,25 +1,26 @@
# 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
-```
-
-It has a saddle point at row 2, column 1.
+An acceptable tree will be the largest in its row, while being the smallest 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 grid might not have any good trees at all.
+Or it might have one, or even several.
-A matrix may have zero or more saddle points.
+Here is a grid that has exactly one candidate tree.
-Your code should be able to provide the (possibly empty) list of all the saddle points for any given matrix.
+```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
+```
-The matrix can have a different number of rows and columns (Non square).
+- 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.
-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 00000000000..34b2c77e0cf
--- /dev/null
+++ b/exercises/practice/saddle-points/.docs/introduction.md
@@ -0,0 +1,11 @@
+# Introduction
+
+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 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.
+
+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/saddle-points/.meta/template.j2 b/exercises/practice/saddle-points/.meta/template.j2
index 35e4ed56b9b..d04e6d0e883 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 24c18e09b7f..78ddf2484d9 100644
--- a/exercises/practice/saddle-points/saddle_points_test.py
+++ b/exercises/practice/saddle-points/saddle_points_test.py
@@ -1,18 +1,13 @@
-"""Tests for the saddle-points exercise
+# 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-19
-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 unittest
from saddle_points import (
saddle_points,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
def sorted_points(point_list):
return sorted(point_list, key=lambda p: (p["row"], p["column"]))
diff --git a/exercises/practice/satellite/.meta/template.j2 b/exercises/practice/satellite/.meta/template.j2
index 3bd41df23aa..d8ee854568a 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 2eb329c2f96..f44a5384798 100644
--- a/exercises/practice/satellite/satellite_test.py
+++ b/exercises/practice/satellite/satellite_test.py
@@ -1,11 +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-19
+
import unittest
from satellite import (
tree_from_traversals,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SatelliteTest(unittest.TestCase):
def test_empty_tree(self):
diff --git a/exercises/practice/say/.docs/instructions.append.md b/exercises/practice/say/.docs/instructions.append.md
index aed8f067747..43365a7397a 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")
```
diff --git a/exercises/practice/say/.meta/template.j2 b/exercises/practice/say/.meta/template.j2
index 77fa67a1ca3..81d61a694c7 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 22deb5364cb..1b9cbdea929 100644
--- a/exercises/practice/say/say_test.py
+++ b/exercises/practice/say/say_test.py
@@ -1,11 +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-19
+
import unittest
from say import (
say,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SayTest(unittest.TestCase):
def test_zero(self):
diff --git a/exercises/practice/scale-generator/.meta/template.j2 b/exercises/practice/scale-generator/.meta/template.j2
index b65b5f09789..d802db09b4f 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,12 +22,8 @@
{% endfor %}
{%- endmacro %}
-{{ macros.header(["Scale"])}}
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 7edc118c778..c2346119b6d 100644
--- a/exercises/practice/scale-generator/scale_generator_test.py
+++ b/exercises/practice/scale-generator/scale_generator_test.py
@@ -1,11 +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-19
+
import unittest
from scale_generator import (
Scale,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ScaleGeneratorTest(unittest.TestCase):
@@ -78,7 +80,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 f122b916b32..0688c940fdd 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,12 +11,9 @@
{{ case["expected"] }}
)
{%- endmacro %}
-{{ macros.header()}}
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 e8d7eff75ea..7121cf640e1 100644
--- a/exercises/practice/scrabble-score/scrabble_score_test.py
+++ b/exercises/practice/scrabble-score/scrabble_score_test.py
@@ -1,11 +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-19
+
import unittest
from scrabble_score import (
score,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ScrabbleScoreTest(unittest.TestCase):
def test_lowercase_letter(self):
@@ -40,7 +42,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/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md
index 77136cf0f7b..d2120b9bf2e 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/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json
index cca237b478e..7972aa1f5a0 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"
}
diff --git a/exercises/practice/secret-handshake/.meta/template.j2 b/exercises/practice/secret-handshake/.meta/template.j2
index 459e9c9875e..b45136a57c2 100644
--- a/exercises/practice/secret-handshake/.meta/template.j2
+++ b/exercises/practice/secret-handshake/.meta/template.j2
@@ -1,10 +1,10 @@
{%- 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 -%}
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 959c866981a..544da4e2718 100644
--- a/exercises/practice/secret-handshake/secret_handshake_test.py
+++ b/exercises/practice/secret-handshake/secret_handshake_test.py
@@ -1,11 +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-19
+
import unittest
from secret_handshake import (
commands,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SecretHandshakeTest(unittest.TestCase):
def test_wink_for_1(self):
@@ -44,7 +46,3 @@ def test_reverse_all_possible_actions(self):
def test_do_nothing_for_zero(self):
self.assertEqual(commands("00000"), [])
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/series/.meta/template.j2 b/exercises/practice/series/.meta/template.j2
index 2e7f60e4fd2..c9ac185ab0c 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 7f6de7a4669..5e0a33d9932 100644
--- a/exercises/practice/series/series_test.py
+++ b/exercises/practice/series/series_test.py
@@ -1,11 +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-19
+
import unittest
from series import (
slices,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SeriesTest(unittest.TestCase):
def test_slices_of_one_from_one(self):
diff --git a/exercises/practice/sgf-parsing/.meta/template.j2 b/exercises/practice/sgf-parsing/.meta/template.j2
index 7017a436480..3e968bec534 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 b3d43ed09df..c33a5dbecff 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-19
+
import unittest
from sgf_parsing import (
@@ -5,8 +9,6 @@
SgfTree,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SgfParsingTest(unittest.TestCase):
def test_empty_input(self):
diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md
index ec14620ce45..3adf1d551be 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/sieve/.meta/template.j2 b/exercises/practice/sieve/.meta/template.j2
index e26e4e4c1ce..d12df86bb15 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 -%}
@@ -7,5 +9,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 d9fa67262b3..d9b88558316 100644
--- a/exercises/practice/sieve/sieve_test.py
+++ b/exercises/practice/sieve/sieve_test.py
@@ -1,11 +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-19
+
import unittest
from sieve import (
primes,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SieveTest(unittest.TestCase):
def test_no_primes_under_two(self):
@@ -194,7 +196,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 ca20aa30114..fa7483a5590 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]") |
@@ -59,5 +62,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 07c0c3d5b5d..bc1efa9f86d 100644
--- a/exercises/practice/simple-cipher/simple_cipher_test.py
+++ b/exercises/practice/simple-cipher/simple_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/simple-cipher/canonical-data.json
+# File last updated on 2023-07-20
+
import re
import unittest
@@ -5,8 +9,6 @@
Cipher,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class RandomKeyCipherTest(unittest.TestCase):
def test_can_encode(self):
@@ -64,7 +66,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/simple-linked-list/.docs/hints.md b/exercises/practice/simple-linked-list/.docs/hints.md
index 4fd7f9ace08..1ecef83ea24 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 8e565516c46..41db11df966 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].
diff --git a/exercises/practice/simple-linked-list/.docs/instructions.md b/exercises/practice/simple-linked-list/.docs/instructions.md
index 4d845fac06c..04640b1fb03 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 00000000000..0e1df72f9bf
--- /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.
diff --git a/exercises/practice/space-age/.meta/template.j2 b/exercises/practice/space-age/.meta/template.j2
index a7822ad6495..d939487f8a3 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):
@@ -9,5 +11,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 b3f91ca0673..ddde365d6bf 100644
--- a/exercises/practice/space-age/space_age_test.py
+++ b/exercises/practice/space-age/space_age_test.py
@@ -1,11 +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-19
+
import unittest
from space_age import (
SpaceAge,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SpaceAgeTest(unittest.TestCase):
def test_age_on_earth(self):
@@ -31,7 +33,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 b501e6d4147..552b48831cf 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 -%}
@@ -7,5 +9,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 1291342a892..49174455977 100644
--- a/exercises/practice/spiral-matrix/spiral_matrix_test.py
+++ b/exercises/practice/spiral-matrix/spiral_matrix_test.py
@@ -1,11 +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-19
+
import unittest
from spiral_matrix import (
spiral_matrix,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SpiralMatrixTest(unittest.TestCase):
def test_empty_spiral(self):
@@ -37,7 +39,3 @@ def test_spiral_of_size_5(self):
[13, 12, 11, 10, 9],
],
)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/exercises/practice/square-root/.meta/template.j2 b/exercises/practice/square-root/.meta/template.j2
index 9334317859f..9038eb171a0 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 635728fee45..8f94940f552 100644
--- a/exercises/practice/square-root/square_root_test.py
+++ b/exercises/practice/square-root/square_root_test.py
@@ -1,11 +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-19
+
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):
diff --git a/exercises/practice/strain/.docs/instructions.md b/exercises/practice/strain/.docs/instructions.md
index 370eb2216fd..3469ae6579d 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/strain/.meta/tests.toml b/exercises/practice/strain/.meta/tests.toml
new file mode 100644
index 00000000000..66ed1044bd6
--- /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
diff --git a/exercises/practice/sublist/.approaches/config.json b/exercises/practice/sublist/.approaches/config.json
new file mode 100644
index 00000000000..ce54db9c14e
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/config.json
@@ -0,0 +1,21 @@
+{
+ "introduction": {
+ "authors": ["safwansamsudeen"]
+ },
+ "approaches": [
+ {
+ "uuid": "db47397a-4551-49e8-8775-7e7aad79a38b",
+ "slug": "list-manipulation",
+ "title": "List manipulation",
+ "blurb": "Manipulate and check lists to solve the exercise",
+ "authors": ["safwansamsudeen"]
+ },
+ {
+ "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",
+ "authors": ["safwansamsudeen"]
+ }
+ ]
+}
diff --git a/exercises/practice/sublist/.approaches/introduction.md b/exercises/practice/sublist/.approaches/introduction.md
new file mode 100644
index 00000000000..42f991ef086
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/introduction.md
@@ -0,0 +1,59 @@
+# 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 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
+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 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
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 00000000000..ac374b730e7
--- /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 00000000000..290f8cdd2b8
--- /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 00000000000..ff960902dc9
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/using-strings/content.md
@@ -0,0 +1,47 @@
+# 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 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
+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
+```
+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`.
+
+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`**.
+
+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
new file mode 100644
index 00000000000..26fc3ec0ec7
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/using-strings/snippet.txt
@@ -0,0 +1,8 @@
+# Failing approach
+def sublist(list_one, list_two):
+ list_one_check = str(list_one).strip("[]")
+ ...
+ elif list_one_check in list_two_check:
+ return SUBLIST
+ ...
+ return UNEQUAL
\ No newline at end of file
diff --git a/exercises/practice/sublist/.meta/template.j2 b/exercises/practice/sublist/.meta/template.j2
index 00c111f3fc7..ecd410bdaa1 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.py b/exercises/practice/sublist/sublist.py
index 428d3aa656c..35410301d53 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/exercises/practice/sublist/sublist_test.py b/exercises/practice/sublist/sublist_test.py
index b3dc9c7bce1..af9a7799c10 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-19
+
import unittest
from sublist import (
@@ -8,8 +12,6 @@
UNEQUAL,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SublistTest(unittest.TestCase):
def test_empty_lists(self):
diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md
index ff7fdffd86a..d69f890e9d6 100644
--- a/exercises/practice/sum-of-multiples/.docs/instructions.md
+++ b/exercises/practice/sum-of-multiples/.docs/instructions.md
@@ -1,7 +1,27 @@
# Instructions
-Given a number, find the sum of all the unique multiples of particular numbers up to but not including that number.
+Your task is to write the code that calculates the energy points that get awarded to players when they complete a level.
-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 points awarded depend on two things:
-The sum of these multiples is 78.
+- 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:
+
+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.
+
+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 level 20.
+
+- 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.
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 00000000000..69cabeed5ab
--- /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.
diff --git a/exercises/practice/sum-of-multiples/.meta/template.j2 b/exercises/practice/sum-of-multiples/.meta/template.j2
index 6ffa327ac1a..03574ec7e8a 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):
@@ -7,5 +9,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 f7e8bfb5409..0057ee7a7f5 100644
--- a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py
+++ b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py
@@ -1,11 +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-19
+
import unittest
from sum_of_multiples import (
sum_of_multiples,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class SumOfMultiplesTest(unittest.TestCase):
def test_no_multiples_within_limit(self):
@@ -57,7 +59,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/tournament/.meta/template.j2 b/exercises/practice/tournament/.meta/template.j2
index 30ca2e94cd0..055b3a89d72 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 22a81667899..622983525dd 100644
--- a/exercises/practice/tournament/tournament_test.py
+++ b/exercises/practice/tournament/tournament_test.py
@@ -1,11 +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-19
+
import unittest
from tournament import (
tally,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class TournamentTest(unittest.TestCase):
def test_just_the_header_if_no_input(self):
diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2
index ef724132e8c..e622947b106 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 0a0f5c024fb..d3ab85ff9ff 100644
--- a/exercises/practice/transpose/transpose_test.py
+++ b/exercises/practice/transpose/transpose_test.py
@@ -1,11 +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-19
+
import unittest
from transpose import (
transpose,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class TransposeTest(unittest.TestCase):
def test_empty_string(self):
diff --git a/exercises/practice/triangle/.meta/template.j2 b/exercises/practice/triangle/.meta/template.j2
index 8875bc51078..b196cfc717f 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 -%}
@@ -9,4 +11,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 2de48d3372e..b279c83c325 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-19
+
import unittest
from triangle import (
@@ -6,8 +10,6 @@
scalene,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class EquilateralTriangleTest(unittest.TestCase):
def test_all_sides_are_equal(self):
@@ -76,7 +78,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()
diff --git a/exercises/practice/trinary/.docs/instructions.md b/exercises/practice/trinary/.docs/instructions.md
index 3638ddb74b1..d38e3b5bbf4 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/trinary/.meta/config.json b/exercises/practice/trinary/.meta/config.json
index 104b505a620..da59ec6c571 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"
}
diff --git a/exercises/practice/twelve-days/.meta/template.j2 b/exercises/practice/twelve-days/.meta/template.j2
index e90fe713074..2baa27d2c42 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 ec097cddb9c..b18c35e7302 100644
--- a/exercises/practice/twelve-days/twelve_days_test.py
+++ b/exercises/practice/twelve-days/twelve_days_test.py
@@ -1,10 +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-19
+
import unittest
from twelve_days import (
recite,
)
-# 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!
diff --git a/exercises/practice/two-bucket/.meta/template.j2 b/exercises/practice/two-bucket/.meta/template.j2
index 02843791272..2cd31976eaa 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 7fe7fac1a7c..b7d1cc01953 100644
--- a/exercises/practice/two-bucket/two_bucket_test.py
+++ b/exercises/practice/two-bucket/two_bucket_test.py
@@ -1,11 +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-21
+
import unittest
from two_bucket import (
measure,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class TwoBucketTest(unittest.TestCase):
def test_measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_one(
@@ -52,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/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md
index a9bb4a3cd3c..37aa75297ea 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. |
diff --git a/exercises/practice/two-fer/.meta/template.j2 b/exercises/practice/two-fer/.meta/template.j2
index 51634eb1055..794d09ad19c 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 -%}
@@ -10,5 +12,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 540930daf3c..fa032f08e3f 100644
--- a/exercises/practice/two-fer/two_fer_test.py
+++ b/exercises/practice/two-fer/two_fer_test.py
@@ -1,11 +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-19
+
import unittest
from two_fer import (
two_fer,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class TwoFerTest(unittest.TestCase):
def test_no_name_given(self):
@@ -16,7 +18,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/variable-length-quantity/.meta/template.j2 b/exercises/practice/variable-length-quantity/.meta/template.j2
index 10b43cbdcc2..c95696ad483 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 c6867dac90a..baeb2365430 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-19
+
import unittest
from variable_length_quantity import (
@@ -5,8 +9,6 @@
encode,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class VariableLengthQuantityTest(unittest.TestCase):
def test_zero(self):
diff --git a/exercises/practice/word-count/.docs/instructions.md b/exercises/practice/word-count/.docs/instructions.md
index 8b7f03ede7a..064393c8a0f 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 00000000000..1654508e79d
--- /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.
diff --git a/exercises/practice/word-count/.meta/template.j2 b/exercises/practice/word-count/.meta/template.j2
index 2c91fdf963d..7f3a6223912 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 -%}
@@ -21,6 +24,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 b63a9eb357a..1bf0bdf6dc7 100644
--- a/exercises/practice/word-count/word_count_test.py
+++ b/exercises/practice/word-count/word_count_test.py
@@ -1,11 +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-19
+
import unittest
from word_count import (
count_words,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class WordCountTest(unittest.TestCase):
def test_count_one_word(self):
@@ -121,7 +123,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 309a2fc1a22..14051f12b3b 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):
@@ -20,6 +22,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 6ee050418d8..d0cbd191d99 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-19
+
import unittest
from word_search import (
@@ -5,8 +9,6 @@
Point,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class WordSearchTest(unittest.TestCase):
def test_should_accept_an_initial_game_grid_and_a_target_search_word(self):
@@ -310,7 +312,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/wordy/.meta/template.j2 b/exercises/practice/wordy/.meta/template.j2
index 00b70a8bb16..74ab335ff92 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 3f85ed41f16..ffcaf49aed4 100644
--- a/exercises/practice/wordy/wordy_test.py
+++ b/exercises/practice/wordy/wordy_test.py
@@ -1,11 +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-19
+
import unittest
from wordy import (
answer,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class WordyTest(unittest.TestCase):
def test_just_a_number(self):
diff --git a/exercises/practice/yacht/.approaches/config.json b/exercises/practice/yacht/.approaches/config.json
new file mode 100644
index 00000000000..86d37075b8b
--- /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 00000000000..2c6bfe527df
--- /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 00000000000..34d270ad895
--- /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 00000000000..581f31d1928
--- /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 00000000000..fb590cc155b
--- /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 00000000000..5a37d16881b
--- /dev/null
+++ b/exercises/practice/yacht/.approaches/introduction.md
@@ -0,0 +1,76 @@
+# 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
+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 00000000000..b49bb6340bd
--- /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 00000000000..4ed99824d8e
--- /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
diff --git a/exercises/practice/yacht/.meta/template.j2 b/exercises/practice/yacht/.meta/template.j2
index 4086f512fc0..af604a42f90 100644
--- a/exercises/practice/yacht/.meta/template.j2
+++ b/exercises/practice/yacht/.meta/template.j2
@@ -1,10 +1,9 @@
{%- import "generator_macros.j2" as macros with context -%}
-import unittest
+{{ macros.canonical_ref() }}
+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):
@@ -16,5 +15,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 5c262c88856..fd2f87ad1f1 100644
--- a/exercises/practice/yacht/yacht_test.py
+++ b/exercises/practice/yacht/yacht_test.py
@@ -1,9 +1,10 @@
-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
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class YachtTest(unittest.TestCase):
def test_yacht(self):
@@ -92,7 +93,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 6b068c7e401..493b7ba7ee4 100644
--- a/exercises/practice/zebra-puzzle/.meta/template.j2
+++ b/exercises/practice/zebra-puzzle/.meta/template.j2
@@ -1,10 +1,10 @@
{%- 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 -%}
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 034d4cccf4d..fd2a331b185 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-19
+
import unittest
from zebra_puzzle import (
@@ -5,8 +9,6 @@
owns_zebra,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ZebraPuzzleTest(unittest.TestCase):
def test_resident_who_drinks_water(self):
@@ -14,7 +16,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 9f239bc1510..5d291d77f0a 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,13 +47,10 @@
{%- 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 %}
{{ test_case(case) }}
{%- endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/zipper/zipper_test.py b/exercises/practice/zipper/zipper_test.py
index 5883e80df6c..702f6ccbcf9 100644
--- a/exercises/practice/zipper/zipper_test.py
+++ b/exercises/practice/zipper/zipper_test.py
@@ -1,11 +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-19
+
import unittest
from zipper import (
Zipper,
)
-# Tests adapted from `problem-specifications//canonical-data.json`
-
class ZipperTest(unittest.TestCase):
def test_data_is_retained(self):
@@ -315,7 +317,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()
diff --git a/exercises/shared/.docs/help.md b/exercises/shared/.docs/help.md
index 0e105b02d94..ef95bd6193b 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.
diff --git a/pylintrc b/pylintrc
index 745e6f039af..09795978bc4 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 b2b12946817..004ded95110 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")
diff --git a/reference/track_exercises_overview.md b/reference/track_exercises_overview.md
index f7a04168033..761f2c47644 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
+
+